Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / ext-all-debug-w-comments.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
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 GNU General Public License Usage
8550 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
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 GNU General Public License Usage
10996 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
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 GNU General Public License Usage
20891 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
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             // start the request!
27381             xhr.send(requestOptions.data);
27382             if (!async) {
27383                 return this.onComplete(request);
27384             }
27385             return request;
27386         } else {
27387             Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
27388             return null;
27389         }
27390     },
27391
27392     /**
27393      * Uploads a form using a hidden iframe.
27394      * @param {String/HTMLElement/Ext.Element} form The form to upload
27395      * @param {String} url The url to post to
27396      * @param {String} params Any extra parameters to pass
27397      * @param {Object} options The initial options
27398      */
27399     upload: function(form, url, params, options) {
27400         form = Ext.getDom(form);
27401         options = options || {};
27402
27403         var id = Ext.id(),
27404                 frame = document.createElement('iframe'),
27405                 hiddens = [],
27406                 encoding = 'multipart/form-data',
27407                 buf = {
27408                     target: form.target,
27409                     method: form.method,
27410                     encoding: form.encoding,
27411                     enctype: form.enctype,
27412                     action: form.action
27413                 }, hiddenItem;
27414
27415         /*
27416          * Originally this behaviour was modified for Opera 10 to apply the secure URL after
27417          * the frame had been added to the document. It seems this has since been corrected in
27418          * Opera so the behaviour has been reverted, the URL will be set before being added.
27419          */
27420         Ext.fly(frame).set({
27421             id: id,
27422             name: id,
27423             cls: Ext.baseCSSPrefix + 'hide-display',
27424             src: Ext.SSL_SECURE_URL
27425         });
27426
27427         document.body.appendChild(frame);
27428
27429         // This is required so that IE doesn't pop the response up in a new window.
27430         if (document.frames) {
27431            document.frames[id].name = id;
27432         }
27433
27434         Ext.fly(form).set({
27435             target: id,
27436             method: 'POST',
27437             enctype: encoding,
27438             encoding: encoding,
27439             action: url || buf.action
27440         });
27441
27442         // add dynamic params
27443         if (params) {
27444             Ext.iterate(Ext.Object.fromQueryString(params), function(name, value){
27445                 hiddenItem = document.createElement('input');
27446                 Ext.fly(hiddenItem).set({
27447                     type: 'hidden',
27448                     value: value,
27449                     name: name
27450                 });
27451                 form.appendChild(hiddenItem);
27452                 hiddens.push(hiddenItem);
27453             });
27454         }
27455
27456         Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
27457         form.submit();
27458
27459         Ext.fly(form).set(buf);
27460         Ext.each(hiddens, function(h) {
27461             Ext.removeNode(h);
27462         });
27463     },
27464
27465     /**
27466      * @private
27467      * Callback handler for the upload function. After we've submitted the form via the iframe this creates a bogus
27468      * response object to simulate an XHR and populates its responseText from the now-loaded iframe's document body
27469      * (or a textarea inside the body). We then clean up by removing the iframe
27470      */
27471     onUploadComplete: function(frame, options) {
27472         var me = this,
27473             // bogus response object
27474             response = {
27475                 responseText: '',
27476                 responseXML: null
27477             }, doc, firstChild;
27478
27479         try {
27480             doc = frame.contentWindow.document || frame.contentDocument || window.frames[frame.id].document;
27481             if (doc) {
27482                 if (doc.body) {
27483                     if (/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
27484                         response.responseText = firstChild.value;
27485                     } else {
27486                         response.responseText = doc.body.innerHTML;
27487                     }
27488                 }
27489                 //in IE the document may still have a body even if returns XML.
27490                 response.responseXML = doc.XMLDocument || doc;
27491             }
27492         } catch (e) {
27493         }
27494
27495         me.fireEvent('requestcomplete', me, response, options);
27496
27497         Ext.callback(options.success, options.scope, [response, options]);
27498         Ext.callback(options.callback, options.scope, [options, true, response]);
27499
27500         setTimeout(function(){
27501             Ext.removeNode(frame);
27502         }, 100);
27503     },
27504
27505     /**
27506      * Detects whether the form is intended to be used for an upload.
27507      * @private
27508      */
27509     isFormUpload: function(options){
27510         var form = this.getForm(options);
27511         if (form) {
27512             return (options.isUpload || (/multipart\/form-data/i).test(form.getAttribute('enctype')));
27513         }
27514         return false;
27515     },
27516
27517     /**
27518      * Gets the form object from options.
27519      * @private
27520      * @param {Object} options The request options
27521      * @return {HTMLElement} The form, null if not passed
27522      */
27523     getForm: function(options){
27524         return Ext.getDom(options.form) || null;
27525     },
27526
27527     /**
27528      * Sets various options such as the url, params for the request
27529      * @param {Object} options The initial options
27530      * @param {Object} scope The scope to execute in
27531      * @return {Object} The params for the request
27532      */
27533     setOptions: function(options, scope){
27534         var me =  this,
27535             params = options.params || {},
27536             extraParams = me.extraParams,
27537             urlParams = options.urlParams,
27538             url = options.url || me.url,
27539             jsonData = options.jsonData,
27540             method,
27541             disableCache,
27542             data;
27543
27544
27545         // allow params to be a method that returns the params object
27546         if (Ext.isFunction(params)) {
27547             params = params.call(scope, options);
27548         }
27549
27550         // allow url to be a method that returns the actual url
27551         if (Ext.isFunction(url)) {
27552             url = url.call(scope, options);
27553         }
27554
27555         url = this.setupUrl(options, url);
27556
27557
27558         // check for xml or json data, and make sure json data is encoded
27559         data = options.rawData || options.xmlData || jsonData || null;
27560         if (jsonData && !Ext.isPrimitive(jsonData)) {
27561             data = Ext.encode(data);
27562         }
27563
27564         // make sure params are a url encoded string and include any extraParams if specified
27565         if (Ext.isObject(params)) {
27566             params = Ext.Object.toQueryString(params);
27567         }
27568
27569         if (Ext.isObject(extraParams)) {
27570             extraParams = Ext.Object.toQueryString(extraParams);
27571         }
27572
27573         params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
27574
27575         urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
27576
27577         params = this.setupParams(options, params);
27578
27579         // decide the proper method for this request
27580         method = (options.method || me.method || ((params || data) ? 'POST' : 'GET')).toUpperCase();
27581         this.setupMethod(options, method);
27582
27583
27584         disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
27585         // if the method is get append date to prevent caching
27586         if (method === 'GET' && disableCache) {
27587             url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
27588         }
27589
27590         // if the method is get or there is json/xml data append the params to the url
27591         if ((method == 'GET' || data) && params) {
27592             url = Ext.urlAppend(url, params);
27593             params = null;
27594         }
27595
27596         // allow params to be forced into the url
27597         if (urlParams) {
27598             url = Ext.urlAppend(url, urlParams);
27599         }
27600
27601         return {
27602             url: url,
27603             method: method,
27604             data: data || params || null
27605         };
27606     },
27607
27608     /**
27609      * Template method for overriding url
27610      * @template
27611      * @private
27612      * @param {Object} options
27613      * @param {String} url
27614      * @return {String} The modified url
27615      */
27616     setupUrl: function(options, url){
27617         var form = this.getForm(options);
27618         if (form) {
27619             url = url || form.action;
27620         }
27621         return url;
27622     },
27623
27624
27625     /**
27626      * Template method for overriding params
27627      * @template
27628      * @private
27629      * @param {Object} options
27630      * @param {String} params
27631      * @return {String} The modified params
27632      */
27633     setupParams: function(options, params) {
27634         var form = this.getForm(options),
27635             serializedForm;
27636         if (form && !this.isFormUpload(options)) {
27637             serializedForm = Ext.Element.serializeForm(form);
27638             params = params ? (params + '&' + serializedForm) : serializedForm;
27639         }
27640         return params;
27641     },
27642
27643     /**
27644      * Template method for overriding method
27645      * @template
27646      * @private
27647      * @param {Object} options
27648      * @param {String} method
27649      * @return {String} The modified method
27650      */
27651     setupMethod: function(options, method){
27652         if (this.isFormUpload(options)) {
27653             return 'POST';
27654         }
27655         return method;
27656     },
27657
27658     /**
27659      * Setup all the headers for the request
27660      * @private
27661      * @param {Object} xhr The xhr object
27662      * @param {Object} options The options for the request
27663      * @param {Object} data The data for the request
27664      * @param {Object} params The params for the request
27665      */
27666     setupHeaders: function(xhr, options, data, params){
27667         var me = this,
27668             headers = Ext.apply({}, options.headers || {}, me.defaultHeaders || {}),
27669             contentType = me.defaultPostHeader,
27670             jsonData = options.jsonData,
27671             xmlData = options.xmlData,
27672             key,
27673             header;
27674
27675         if (!headers['Content-Type'] && (data || params)) {
27676             if (data) {
27677                 if (options.rawData) {
27678                     contentType = 'text/plain';
27679                 } else {
27680                     if (xmlData && Ext.isDefined(xmlData)) {
27681                         contentType = 'text/xml';
27682                     } else if (jsonData && Ext.isDefined(jsonData)) {
27683                         contentType = 'application/json';
27684                     }
27685                 }
27686             }
27687             headers['Content-Type'] = contentType;
27688         }
27689
27690         if (me.useDefaultXhrHeader && !headers['X-Requested-With']) {
27691             headers['X-Requested-With'] = me.defaultXhrHeader;
27692         }
27693         // set up all the request headers on the xhr object
27694         try{
27695             for (key in headers) {
27696                 if (headers.hasOwnProperty(key)) {
27697                     header = headers[key];
27698                     xhr.setRequestHeader(key, header);
27699                 }
27700
27701             }
27702         } catch(e) {
27703             me.fireEvent('exception', key, header);
27704         }
27705         return headers;
27706     },
27707
27708     /**
27709      * Creates the appropriate XHR transport for the browser.
27710      * @private
27711      */
27712     getXhrInstance: (function(){
27713         var options = [function(){
27714             return new XMLHttpRequest();
27715         }, function(){
27716             return new ActiveXObject('MSXML2.XMLHTTP.3.0');
27717         }, function(){
27718             return new ActiveXObject('MSXML2.XMLHTTP');
27719         }, function(){
27720             return new ActiveXObject('Microsoft.XMLHTTP');
27721         }], i = 0,
27722             len = options.length,
27723             xhr;
27724
27725         for(; i < len; ++i) {
27726             try{
27727                 xhr = options[i];
27728                 xhr();
27729                 break;
27730             }catch(e){}
27731         }
27732         return xhr;
27733     })(),
27734
27735     /**
27736      * Determines whether this object has a request outstanding.
27737      * @param {Object} [request] Defaults to the last transaction
27738      * @return {Boolean} True if there is an outstanding request.
27739      */
27740     isLoading : function(request) {
27741         if (!request) {
27742             request = this.getLatest();
27743         }
27744         if (!(request && request.xhr)) {
27745             return false;
27746         }
27747         // if there is a connection and readyState is not 0 or 4
27748         var state = request.xhr.readyState;
27749         return !(state === 0 || state == 4);
27750     },
27751
27752     /**
27753      * Aborts an active request.
27754      * @param {Object} [request] Defaults to the last request
27755      */
27756     abort : function(request) {
27757         var me = this;
27758         
27759         if (!request) {
27760             request = me.getLatest();
27761         }
27762
27763         if (request && me.isLoading(request)) {
27764             /*
27765              * Clear out the onreadystatechange here, this allows us
27766              * greater control, the browser may/may not fire the function
27767              * depending on a series of conditions.
27768              */
27769             request.xhr.onreadystatechange = null;
27770             request.xhr.abort();
27771             me.clearTimeout(request);
27772             if (!request.timedout) {
27773                 request.aborted = true;
27774             }
27775             me.onComplete(request);
27776             me.cleanup(request);
27777         }
27778     },
27779     
27780     /**
27781      * Aborts all active requests
27782      */
27783     abortAll: function(){
27784         var requests = this.requests,
27785             id;
27786         
27787         for (id in requests) {
27788             if (requests.hasOwnProperty(id)) {
27789                 this.abort(requests[id]);
27790             }
27791         }
27792     },
27793     
27794     /**
27795      * Gets the most recent request
27796      * @private
27797      * @return {Object} The request. Null if there is no recent request
27798      */
27799     getLatest: function(){
27800         var id = this.latestId,
27801             request;
27802             
27803         if (id) {
27804             request = this.requests[id];
27805         }
27806         return request || null;
27807     },
27808
27809     /**
27810      * Fires when the state of the xhr changes
27811      * @private
27812      * @param {Object} request The request
27813      */
27814     onStateChange : function(request) {
27815         if (request.xhr.readyState == 4) {
27816             this.clearTimeout(request);
27817             this.onComplete(request);
27818             this.cleanup(request);
27819         }
27820     },
27821
27822     /**
27823      * Clears the timeout on the request
27824      * @private
27825      * @param {Object} The request
27826      */
27827     clearTimeout: function(request){
27828         clearTimeout(request.timeout);
27829         delete request.timeout;
27830     },
27831
27832     /**
27833      * Cleans up any left over information from the request
27834      * @private
27835      * @param {Object} The request
27836      */
27837     cleanup: function(request){
27838         request.xhr = null;
27839         delete request.xhr;
27840     },
27841
27842     /**
27843      * To be called when the request has come back from the server
27844      * @private
27845      * @param {Object} request
27846      * @return {Object} The response
27847      */
27848     onComplete : function(request) {
27849         var me = this,
27850             options = request.options,
27851             result,
27852             success,
27853             response;
27854
27855         try {
27856             result = me.parseStatus(request.xhr.status);
27857         } catch (e) {
27858             // in some browsers we can't access the status if the readyState is not 4, so the request has failed
27859             result = {
27860                 success : false,
27861                 isException : false
27862             };
27863         }
27864         success = result.success;
27865
27866         if (success) {
27867             response = me.createResponse(request);
27868             me.fireEvent('requestcomplete', me, response, options);
27869             Ext.callback(options.success, options.scope, [response, options]);
27870         } else {
27871             if (result.isException || request.aborted || request.timedout) {
27872                 response = me.createException(request);
27873             } else {
27874                 response = me.createResponse(request);
27875             }
27876             me.fireEvent('requestexception', me, response, options);
27877             Ext.callback(options.failure, options.scope, [response, options]);
27878         }
27879         Ext.callback(options.callback, options.scope, [options, success, response]);
27880         delete me.requests[request.id];
27881         return response;
27882     },
27883
27884     /**
27885      * Checks if the response status was successful
27886      * @param {Number} status The status code
27887      * @return {Object} An object containing success/status state
27888      */
27889     parseStatus: function(status) {
27890         // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
27891         status = status == 1223 ? 204 : status;
27892
27893         var success = (status >= 200 && status < 300) || status == 304,
27894             isException = false;
27895
27896         if (!success) {
27897             switch (status) {
27898                 case 12002:
27899                 case 12029:
27900                 case 12030:
27901                 case 12031:
27902                 case 12152:
27903                 case 13030:
27904                     isException = true;
27905                     break;
27906             }
27907         }
27908         return {
27909             success: success,
27910             isException: isException
27911         };
27912     },
27913
27914     /**
27915      * Creates the response object
27916      * @private
27917      * @param {Object} request
27918      */
27919     createResponse : function(request) {
27920         var xhr = request.xhr,
27921             headers = {},
27922             lines = xhr.getAllResponseHeaders().replace(/\r\n/g, '\n').split('\n'),
27923             count = lines.length,
27924             line, index, key, value, response;
27925
27926         while (count--) {
27927             line = lines[count];
27928             index = line.indexOf(':');
27929             if(index >= 0) {
27930                 key = line.substr(0, index).toLowerCase();
27931                 if (line.charAt(index + 1) == ' ') {
27932                     ++index;
27933                 }
27934                 headers[key] = line.substr(index + 1);
27935             }
27936         }
27937
27938         request.xhr = null;
27939         delete request.xhr;
27940
27941         response = {
27942             request: request,
27943             requestId : request.id,
27944             status : xhr.status,
27945             statusText : xhr.statusText,
27946             getResponseHeader : function(header){ return headers[header.toLowerCase()]; },
27947             getAllResponseHeaders : function(){ return headers; },
27948             responseText : xhr.responseText,
27949             responseXML : xhr.responseXML
27950         };
27951
27952         // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
27953         // functions created with getResponseHeader/getAllResponseHeaders
27954         xhr = null;
27955         return response;
27956     },
27957
27958     /**
27959      * Creates the exception object
27960      * @private
27961      * @param {Object} request
27962      */
27963     createException : function(request) {
27964         return {
27965             request : request,
27966             requestId : request.id,
27967             status : request.aborted ? -1 : 0,
27968             statusText : request.aborted ? 'transaction aborted' : 'communication failure',
27969             aborted: request.aborted,
27970             timedout: request.timedout
27971         };
27972     }
27973 });
27974
27975 /**
27976  * @class Ext.Ajax
27977  * @singleton
27978  * @markdown
27979  * @extends Ext.data.Connection
27980
27981 A singleton instance of an {@link Ext.data.Connection}. This class
27982 is used to communicate with your server side code. It can be used as follows:
27983
27984     Ext.Ajax.request({
27985         url: 'page.php',
27986         params: {
27987             id: 1
27988         },
27989         success: function(response){
27990             var text = response.responseText;
27991             // process server response here
27992         }
27993     });
27994
27995 Default options for all requests can be set by changing a property on the Ext.Ajax class:
27996
27997     Ext.Ajax.timeout = 60000; // 60 seconds
27998
27999 Any options specified in the request method for the Ajax request will override any
28000 defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
28001 request will be 60 seconds.
28002
28003     Ext.Ajax.timeout = 120000; // 120 seconds
28004     Ext.Ajax.request({
28005         url: 'page.aspx',
28006         timeout: 60000
28007     });
28008
28009 In general, this class will be used for all Ajax requests in your application.
28010 The main reason for creating a separate {@link Ext.data.Connection} is for a
28011 series of requests that share common settings that are different to all other
28012 requests in the application.
28013
28014  */
28015 Ext.define('Ext.Ajax', {
28016     extend: 'Ext.data.Connection',
28017     singleton: true,
28018
28019     /**
28020      * @cfg {String} url @hide
28021      */
28022     /**
28023      * @cfg {Object} extraParams @hide
28024      */
28025     /**
28026      * @cfg {Object} defaultHeaders @hide
28027      */
28028     /**
28029      * @cfg {String} method (Optional) @hide
28030      */
28031     /**
28032      * @cfg {Number} timeout (Optional) @hide
28033      */
28034     /**
28035      * @cfg {Boolean} autoAbort (Optional) @hide
28036      */
28037
28038     /**
28039      * @cfg {Boolean} disableCaching (Optional) @hide
28040      */
28041
28042     /**
28043      * @property {Boolean} disableCaching
28044      * True to add a unique cache-buster param to GET requests. Defaults to true.
28045      */
28046     /**
28047      * @property {String} url
28048      * The default URL to be used for requests to the server.
28049      * If the server receives all requests through one URL, setting this once is easier than
28050      * entering it on every request.
28051      */
28052     /**
28053      * @property {Object} extraParams
28054      * An object containing properties which are used as extra parameters to each request made
28055      * by this object. Session information and other data that you need
28056      * to pass with each request are commonly put here.
28057      */
28058     /**
28059      * @property {Object} defaultHeaders
28060      * An object containing request headers which are added to each request made by this object.
28061      */
28062     /**
28063      * @property {String} method
28064      * The default HTTP method to be used for requests. Note that this is case-sensitive and
28065      * should be all caps (if not set but params are present will use
28066      * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
28067      */
28068     /**
28069      * @property {Number} timeout
28070      * The timeout in milliseconds to be used for requests. Defaults to 30000.
28071      */
28072
28073     /**
28074      * @property {Boolean} autoAbort
28075      * Whether a new request should abort any pending requests.
28076      */
28077     autoAbort : false
28078 });
28079 /**
28080  * A class used to load remote content to an Element. Sample usage:
28081  *
28082  *     Ext.get('el').load({
28083  *         url: 'myPage.php',
28084  *         scripts: true,
28085  *         params: {
28086  *             id: 1
28087  *         }
28088  *     });
28089  *
28090  * In general this class will not be instanced directly, rather the {@link Ext.Element#load} method
28091  * will be used.
28092  */
28093 Ext.define('Ext.ElementLoader', {
28094
28095     /* Begin Definitions */
28096
28097     mixins: {
28098         observable: 'Ext.util.Observable'
28099     },
28100
28101     uses: [
28102         'Ext.data.Connection',
28103         'Ext.Ajax'
28104     ],
28105
28106     statics: {
28107         Renderer: {
28108             Html: function(loader, response, active){
28109                 loader.getTarget().update(response.responseText, active.scripts === true);
28110                 return true;
28111             }
28112         }
28113     },
28114
28115     /* End Definitions */
28116
28117     /**
28118      * @cfg {String} url
28119      * The url to retrieve the content from.
28120      */
28121     url: null,
28122
28123     /**
28124      * @cfg {Object} params
28125      * Any params to be attached to the Ajax request. These parameters will
28126      * be overridden by any params in the load options.
28127      */
28128     params: null,
28129
28130     /**
28131      * @cfg {Object} baseParams Params that will be attached to every request. These parameters
28132      * will not be overridden by any params in the load options.
28133      */
28134     baseParams: null,
28135
28136     /**
28137      * @cfg {Boolean/Object} autoLoad
28138      * True to have the loader make a request as soon as it is created.
28139      * This argument can also be a set of options that will be passed to {@link #load} is called.
28140      */
28141     autoLoad: false,
28142
28143     /**
28144      * @cfg {HTMLElement/Ext.Element/String} target
28145      * The target element for the loader. It can be the DOM element, the id or an {@link Ext.Element}.
28146      */
28147     target: null,
28148
28149     /**
28150      * @cfg {Boolean/String} loadMask
28151      * True or a string to show when the element is loading.
28152      */
28153     loadMask: false,
28154
28155     /**
28156      * @cfg {Object} ajaxOptions
28157      * Any additional options to be passed to the request, for example timeout or headers.
28158      */
28159     ajaxOptions: null,
28160
28161     /**
28162      * @cfg {Boolean} scripts
28163      * True to parse any inline script tags in the response.
28164      */
28165     scripts: false,
28166
28167     /**
28168      * @cfg {Function} success
28169      * A function to be called when a load request is successful.
28170      * Will be called with the following config parameters:
28171      *
28172      * - this - The ElementLoader instance.
28173      * - response - The response object.
28174      * - options - Ajax options.
28175      */
28176
28177     /**
28178      * @cfg {Function} failure A function to be called when a load request fails.
28179      * Will be called with the following config parameters:
28180      *
28181      * - this - The ElementLoader instance.
28182      * - response - The response object.
28183      * - options - Ajax options.
28184      */
28185
28186     /**
28187      * @cfg {Function} callback A function to be called when a load request finishes.
28188      * Will be called with the following config parameters:
28189      *
28190      * - this - The ElementLoader instance.
28191      * - success - True if successful request.
28192      * - response - The response object.
28193      * - options - Ajax options.
28194      */
28195
28196     /**
28197      * @cfg {Object} scope
28198      * The scope to execute the {@link #success} and {@link #failure} functions in.
28199      */
28200
28201     /**
28202      * @cfg {Function} renderer
28203      * A custom function to render the content to the element. The passed parameters are:
28204      *
28205      * - The loader
28206      * - The response
28207      * - The active request
28208      */
28209
28210     isLoader: true,
28211
28212     constructor: function(config) {
28213         var me = this,
28214             autoLoad;
28215
28216         config = config || {};
28217         Ext.apply(me, config);
28218         me.setTarget(me.target);
28219         me.addEvents(
28220             /**
28221              * @event beforeload
28222              * Fires before a load request is made to the server.
28223              * Returning false from an event listener can prevent the load
28224              * from occurring.
28225              * @param {Ext.ElementLoader} this
28226              * @param {Object} options The options passed to the request
28227              */
28228             'beforeload',
28229
28230             /**
28231              * @event exception
28232              * Fires after an unsuccessful load.
28233              * @param {Ext.ElementLoader} this
28234              * @param {Object} response The response from the server
28235              * @param {Object} options The options passed to the request
28236              */
28237             'exception',
28238
28239             /**
28240              * @event load
28241              * Fires after a successful load.
28242              * @param {Ext.ElementLoader} this
28243              * @param {Object} response The response from the server
28244              * @param {Object} options The options passed to the request
28245              */
28246             'load'
28247         );
28248
28249         // don't pass config because we have already applied it.
28250         me.mixins.observable.constructor.call(me);
28251
28252         if (me.autoLoad) {
28253             autoLoad = me.autoLoad;
28254             if (autoLoad === true) {
28255                 autoLoad = {};
28256             }
28257             me.load(autoLoad);
28258         }
28259     },
28260
28261     /**
28262      * Sets an {@link Ext.Element} as the target of this loader.
28263      * Note that if the target is changed, any active requests will be aborted.
28264      * @param {String/HTMLElement/Ext.Element} target The element or its ID.
28265      */
28266     setTarget: function(target){
28267         var me = this;
28268         target = Ext.get(target);
28269         if (me.target && me.target != target) {
28270             me.abort();
28271         }
28272         me.target = target;
28273     },
28274
28275     /**
28276      * Returns the target of this loader.
28277      * @return {Ext.Component} The target or null if none exists.
28278      */
28279     getTarget: function(){
28280         return this.target || null;
28281     },
28282
28283     /**
28284      * Aborts the active load request
28285      */
28286     abort: function(){
28287         var active = this.active;
28288         if (active !== undefined) {
28289             Ext.Ajax.abort(active.request);
28290             if (active.mask) {
28291                 this.removeMask();
28292             }
28293             delete this.active;
28294         }
28295     },
28296
28297     /**
28298      * Removes the mask on the target
28299      * @private
28300      */
28301     removeMask: function(){
28302         this.target.unmask();
28303     },
28304
28305     /**
28306      * Adds the mask on the target
28307      * @private
28308      * @param {Boolean/Object} mask The mask configuration
28309      */
28310     addMask: function(mask){
28311         this.target.mask(mask === true ? null : mask);
28312     },
28313
28314     /**
28315      * Loads new data from the server.
28316      * @param {Object} options The options for the request. They can be any configuration option that can be specified for
28317      * the class, with the exception of the target option. Note that any options passed to the method will override any
28318      * class defaults.
28319      */
28320     load: function(options) {
28321
28322         options = Ext.apply({}, options);
28323
28324         var me = this,
28325             target = me.target,
28326             mask = Ext.isDefined(options.loadMask) ? options.loadMask : me.loadMask,
28327             params = Ext.apply({}, options.params),
28328             ajaxOptions = Ext.apply({}, options.ajaxOptions),
28329             callback = options.callback || me.callback,
28330             scope = options.scope || me.scope || me,
28331             request;
28332
28333         Ext.applyIf(ajaxOptions, me.ajaxOptions);
28334         Ext.applyIf(options, ajaxOptions);
28335
28336         Ext.applyIf(params, me.params);
28337         Ext.apply(params, me.baseParams);
28338
28339         Ext.applyIf(options, {
28340             url: me.url
28341         });
28342
28343
28344         Ext.apply(options, {
28345             scope: me,
28346             params: params,
28347             callback: me.onComplete
28348         });
28349
28350         if (me.fireEvent('beforeload', me, options) === false) {
28351             return;
28352         }
28353
28354         if (mask) {
28355             me.addMask(mask);
28356         }
28357
28358         request = Ext.Ajax.request(options);
28359         me.active = {
28360             request: request,
28361             options: options,
28362             mask: mask,
28363             scope: scope,
28364             callback: callback,
28365             success: options.success || me.success,
28366             failure: options.failure || me.failure,
28367             renderer: options.renderer || me.renderer,
28368             scripts: Ext.isDefined(options.scripts) ? options.scripts : me.scripts
28369         };
28370         me.setOptions(me.active, options);
28371     },
28372
28373     /**
28374      * Sets any additional options on the active request
28375      * @private
28376      * @param {Object} active The active request
28377      * @param {Object} options The initial options
28378      */
28379     setOptions: Ext.emptyFn,
28380
28381     /**
28382      * Parses the response after the request completes
28383      * @private
28384      * @param {Object} options Ajax options
28385      * @param {Boolean} success Success status of the request
28386      * @param {Object} response The response object
28387      */
28388     onComplete: function(options, success, response) {
28389         var me = this,
28390             active = me.active,
28391             scope = active.scope,
28392             renderer = me.getRenderer(active.renderer);
28393
28394
28395         if (success) {
28396             success = renderer.call(me, me, response, active);
28397         }
28398
28399         if (success) {
28400             Ext.callback(active.success, scope, [me, response, options]);
28401             me.fireEvent('load', me, response, options);
28402         } else {
28403             Ext.callback(active.failure, scope, [me, response, options]);
28404             me.fireEvent('exception', me, response, options);
28405         }
28406         Ext.callback(active.callback, scope, [me, success, response, options]);
28407
28408         if (active.mask) {
28409             me.removeMask();
28410         }
28411
28412         delete me.active;
28413     },
28414
28415     /**
28416      * Gets the renderer to use
28417      * @private
28418      * @param {String/Function} renderer The renderer to use
28419      * @return {Function} A rendering function to use.
28420      */
28421     getRenderer: function(renderer){
28422         if (Ext.isFunction(renderer)) {
28423             return renderer;
28424         }
28425         return this.statics().Renderer.Html;
28426     },
28427
28428     /**
28429      * Automatically refreshes the content over a specified period.
28430      * @param {Number} interval The interval to refresh in ms.
28431      * @param {Object} options (optional) The options to pass to the load method. See {@link #load}
28432      */
28433     startAutoRefresh: function(interval, options){
28434         var me = this;
28435         me.stopAutoRefresh();
28436         me.autoRefresh = setInterval(function(){
28437             me.load(options);
28438         }, interval);
28439     },
28440
28441     /**
28442      * Clears any auto refresh. See {@link #startAutoRefresh}.
28443      */
28444     stopAutoRefresh: function(){
28445         clearInterval(this.autoRefresh);
28446         delete this.autoRefresh;
28447     },
28448
28449     /**
28450      * Checks whether the loader is automatically refreshing. See {@link #startAutoRefresh}.
28451      * @return {Boolean} True if the loader is automatically refreshing
28452      */
28453     isAutoRefreshing: function(){
28454         return Ext.isDefined(this.autoRefresh);
28455     },
28456
28457     /**
28458      * Destroys the loader. Any active requests will be aborted.
28459      */
28460     destroy: function(){
28461         var me = this;
28462         me.stopAutoRefresh();
28463         delete me.target;
28464         me.abort();
28465         me.clearListeners();
28466     }
28467 });
28468
28469 /**
28470  * @class Ext.ComponentLoader
28471  * @extends Ext.ElementLoader
28472  *
28473  * This class is used to load content via Ajax into a {@link Ext.Component}. In general
28474  * this class will not be instanced directly, rather a loader configuration will be passed to the
28475  * constructor of the {@link Ext.Component}.
28476  *
28477  * ## HTML Renderer
28478  * By default, the content loaded will be processed as raw html. The response text
28479  * from the request is taken and added to the component. This can be used in
28480  * conjunction with the {@link #scripts} option to execute any inline scripts in
28481  * the resulting content. Using this renderer has the same effect as passing the
28482  * {@link Ext.Component#html} configuration option.
28483  *
28484  * ## Data Renderer
28485  * This renderer allows content to be added by using JSON data and a {@link Ext.XTemplate}.
28486  * The content received from the response is passed to the {@link Ext.Component#update} method.
28487  * This content is run through the attached {@link Ext.Component#tpl} and the data is added to
28488  * the Component. Using this renderer has the same effect as using the {@link Ext.Component#data}
28489  * configuration in conjunction with a {@link Ext.Component#tpl}.
28490  *
28491  * ## Component Renderer
28492  * This renderer can only be used with a {@link Ext.container.Container} and subclasses. It allows for
28493  * Components to be loaded remotely into a Container. The response is expected to be a single/series of
28494  * {@link Ext.Component} configuration objects. When the response is received, the data is decoded
28495  * and then passed to {@link Ext.container.Container#add}. Using this renderer has the same effect as specifying
28496  * the {@link Ext.container.Container#items} configuration on a Container.
28497  *
28498  * ## Custom Renderer
28499  * A custom function can be passed to handle any other special case, see the {@link #renderer} option.
28500  *
28501  * ## Example Usage
28502  *     new Ext.Component({
28503  *         tpl: '{firstName} - {lastName}',
28504  *         loader: {
28505  *             url: 'myPage.php',
28506  *             renderer: 'data',
28507  *             params: {
28508  *                 userId: 1
28509  *             }
28510  *         }
28511  *     });
28512  */
28513 Ext.define('Ext.ComponentLoader', {
28514
28515     /* Begin Definitions */
28516
28517     extend: 'Ext.ElementLoader',
28518
28519     statics: {
28520         Renderer: {
28521             Data: function(loader, response, active){
28522                 var success = true;
28523                 try {
28524                     loader.getTarget().update(Ext.decode(response.responseText));
28525                 } catch (e) {
28526                     success = false;
28527                 }
28528                 return success;
28529             },
28530
28531             Component: function(loader, response, active){
28532                 var success = true,
28533                     target = loader.getTarget(),
28534                     items = [];
28535
28536
28537                 try {
28538                     items = Ext.decode(response.responseText);
28539                 } catch (e) {
28540                     success = false;
28541                 }
28542
28543                 if (success) {
28544                     if (active.removeAll) {
28545                         target.removeAll();
28546                     }
28547                     target.add(items);
28548                 }
28549                 return success;
28550             }
28551         }
28552     },
28553
28554     /* End Definitions */
28555
28556     /**
28557      * @cfg {Ext.Component/String} target The target {@link Ext.Component} for the loader.
28558      * If a string is passed it will be looked up via the id.
28559      */
28560     target: null,
28561
28562     /**
28563      * @cfg {Boolean/Object} loadMask True or a {@link Ext.LoadMask} configuration to enable masking during loading.
28564      */
28565     loadMask: false,
28566
28567     /**
28568      * @cfg {Boolean} scripts True to parse any inline script tags in the response. This only used when using the html
28569      * {@link #renderer}.
28570      */
28571
28572     /**
28573      * @cfg {String/Function} renderer
28574
28575 The type of content that is to be loaded into, which can be one of 3 types:
28576
28577 + **html** : Loads raw html content, see {@link Ext.Component#html}
28578 + **data** : Loads raw html content, see {@link Ext.Component#data}
28579 + **component** : Loads child {Ext.Component} instances. This option is only valid when used with a Container.
28580
28581 Alternatively, you can pass a function which is called with the following parameters.
28582
28583 + loader - Loader instance
28584 + response - The server response
28585 + active - The active request
28586
28587 The function must return false is loading is not successful. Below is a sample of using a custom renderer:
28588
28589     new Ext.Component({
28590         loader: {
28591             url: 'myPage.php',
28592             renderer: function(loader, response, active) {
28593                 var text = response.responseText;
28594                 loader.getTarget().update('The response is ' + text);
28595                 return true;
28596             }
28597         }
28598     });
28599      */
28600     renderer: 'html',
28601
28602     /**
28603      * Set a {Ext.Component} as the target of this loader. Note that if the target is changed,
28604      * any active requests will be aborted.
28605      * @param {String/Ext.Component} target The component to be the target of this loader. If a string is passed
28606      * it will be looked up via its id.
28607      */
28608     setTarget: function(target){
28609         var me = this;
28610
28611         if (Ext.isString(target)) {
28612             target = Ext.getCmp(target);
28613         }
28614
28615         if (me.target && me.target != target) {
28616             me.abort();
28617         }
28618         me.target = target;
28619     },
28620
28621     // inherit docs
28622     removeMask: function(){
28623         this.target.setLoading(false);
28624     },
28625
28626     /**
28627      * Add the mask on the target
28628      * @private
28629      * @param {Boolean/Object} mask The mask configuration
28630      */
28631     addMask: function(mask){
28632         this.target.setLoading(mask);
28633     },
28634
28635     /**
28636      * Get the target of this loader.
28637      * @return {Ext.Component} target The target, null if none exists.
28638      */
28639
28640     setOptions: function(active, options){
28641         active.removeAll = Ext.isDefined(options.removeAll) ? options.removeAll : this.removeAll;
28642     },
28643
28644     /**
28645      * Gets the renderer to use
28646      * @private
28647      * @param {String/Function} renderer The renderer to use
28648      * @return {Function} A rendering function to use.
28649      */
28650     getRenderer: function(renderer){
28651         if (Ext.isFunction(renderer)) {
28652             return renderer;
28653         }
28654
28655         var renderers = this.statics().Renderer;
28656         switch (renderer) {
28657             case 'component':
28658                 return renderers.Component;
28659             case 'data':
28660                 return renderers.Data;
28661             default:
28662                 return Ext.ElementLoader.Renderer.Html;
28663         }
28664     }
28665 });
28666
28667 /**
28668  * @author Ed Spencer
28669  *
28670  * Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
28671  * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
28672  * express like this:
28673  *
28674  *     Ext.define('User', {
28675  *         extend: 'Ext.data.Model',
28676  *         fields: ['id', 'name', 'email'],
28677  *
28678  *         hasMany: {model: 'Order', name: 'orders'}
28679  *     });
28680  *
28681  *     Ext.define('Order', {
28682  *         extend: 'Ext.data.Model',
28683  *         fields: ['id', 'user_id', 'status', 'price'],
28684  *
28685  *         belongsTo: 'User'
28686  *     });
28687  *
28688  * We've set up two models - User and Order - and told them about each other. You can set up as many associations on
28689  * each Model as you need using the two default types - {@link Ext.data.HasManyAssociation hasMany} and {@link
28690  * Ext.data.BelongsToAssociation belongsTo}. There's much more detail on the usage of each of those inside their
28691  * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.
28692  *
28693  * **Further Reading**
28694  *
28695  *   - {@link Ext.data.HasManyAssociation hasMany associations}
28696  *   - {@link Ext.data.BelongsToAssociation belongsTo associations}
28697  *   - {@link Ext.data.Model using Models}
28698  *
28699  * # Self association models
28700  *
28701  * We can also have models that create parent/child associations between the same type. Below is an example, where
28702  * groups can be nested inside other groups:
28703  *
28704  *     // Server Data
28705  *     {
28706  *         "groups": {
28707  *             "id": 10,
28708  *             "parent_id": 100,
28709  *             "name": "Main Group",
28710  *             "parent_group": {
28711  *                 "id": 100,
28712  *                 "parent_id": null,
28713  *                 "name": "Parent Group"
28714  *             },
28715  *             "child_groups": [{
28716  *                 "id": 2,
28717  *                 "parent_id": 10,
28718  *                 "name": "Child Group 1"
28719  *             },{
28720  *                 "id": 3,
28721  *                 "parent_id": 10,
28722  *                 "name": "Child Group 2"
28723  *             },{
28724  *                 "id": 4,
28725  *                 "parent_id": 10,
28726  *                 "name": "Child Group 3"
28727  *             }]
28728  *         }
28729  *     }
28730  *
28731  *     // Client code
28732  *     Ext.define('Group', {
28733  *         extend: 'Ext.data.Model',
28734  *         fields: ['id', 'parent_id', 'name'],
28735  *         proxy: {
28736  *             type: 'ajax',
28737  *             url: 'data.json',
28738  *             reader: {
28739  *                 type: 'json',
28740  *                 root: 'groups'
28741  *             }
28742  *         },
28743  *         associations: [{
28744  *             type: 'hasMany',
28745  *             model: 'Group',
28746  *             primaryKey: 'id',
28747  *             foreignKey: 'parent_id',
28748  *             autoLoad: true,
28749  *             associationKey: 'child_groups' // read child data from child_groups
28750  *         }, {
28751  *             type: 'belongsTo',
28752  *             model: 'Group',
28753  *             primaryKey: 'id',
28754  *             foreignKey: 'parent_id',
28755  *             associationKey: 'parent_group' // read parent data from parent_group
28756  *         }]
28757  *     });
28758  *
28759  *     Ext.onReady(function(){
28760  *
28761  *         Group.load(10, {
28762  *             success: function(group){
28763  *                 console.log(group.getGroup().get('name'));
28764  *
28765  *                 group.groups().each(function(rec){
28766  *                     console.log(rec.get('name'));
28767  *                 });
28768  *             }
28769  *         });
28770  *
28771  *     });
28772  *
28773  */
28774 Ext.define('Ext.data.Association', {
28775     /**
28776      * @cfg {String} ownerModel (required)
28777      * The string name of the model that owns the association.
28778      */
28779
28780     /**
28781      * @cfg {String} associatedModel (required)
28782      * The string name of the model that is being associated with.
28783      */
28784
28785     /**
28786      * @cfg {String} primaryKey
28787      * The name of the primary key on the associated model. In general this will be the
28788      * {@link Ext.data.Model#idProperty} of the Model.
28789      */
28790     primaryKey: 'id',
28791
28792     /**
28793      * @cfg {Ext.data.reader.Reader} reader
28794      * A special reader to read associated data
28795      */
28796     
28797     /**
28798      * @cfg {String} associationKey
28799      * The name of the property in the data to read the association from. Defaults to the name of the associated model.
28800      */
28801
28802     defaultReaderType: 'json',
28803
28804     statics: {
28805         create: function(association){
28806             if (!association.isAssociation) {
28807                 if (Ext.isString(association)) {
28808                     association = {
28809                         type: association
28810                     };
28811                 }
28812
28813                 switch (association.type) {
28814                     case 'belongsTo':
28815                         return Ext.create('Ext.data.BelongsToAssociation', association);
28816                     case 'hasMany':
28817                         return Ext.create('Ext.data.HasManyAssociation', association);
28818                     //TODO Add this back when it's fixed
28819 //                    case 'polymorphic':
28820 //                        return Ext.create('Ext.data.PolymorphicAssociation', association);
28821                     default:
28822                 }
28823             }
28824             return association;
28825         }
28826     },
28827
28828     /**
28829      * Creates the Association object.
28830      * @param {Object} [config] Config object.
28831      */
28832     constructor: function(config) {
28833         Ext.apply(this, config);
28834
28835         var types           = Ext.ModelManager.types,
28836             ownerName       = config.ownerModel,
28837             associatedName  = config.associatedModel,
28838             ownerModel      = types[ownerName],
28839             associatedModel = types[associatedName],
28840             ownerProto;
28841
28842
28843         this.ownerModel = ownerModel;
28844         this.associatedModel = associatedModel;
28845
28846         /**
28847          * @property {String} ownerName
28848          * The name of the model that 'owns' the association
28849          */
28850
28851         /**
28852          * @property {String} associatedName
28853          * The name of the model is on the other end of the association (e.g. if a User model hasMany Orders, this is
28854          * 'Order')
28855          */
28856
28857         Ext.applyIf(this, {
28858             ownerName : ownerName,
28859             associatedName: associatedName
28860         });
28861     },
28862
28863     /**
28864      * Get a specialized reader for reading associated data
28865      * @return {Ext.data.reader.Reader} The reader, null if not supplied
28866      */
28867     getReader: function(){
28868         var me = this,
28869             reader = me.reader,
28870             model = me.associatedModel;
28871
28872         if (reader) {
28873             if (Ext.isString(reader)) {
28874                 reader = {
28875                     type: reader
28876                 };
28877             }
28878             if (reader.isReader) {
28879                 reader.setModel(model);
28880             } else {
28881                 Ext.applyIf(reader, {
28882                     model: model,
28883                     type : me.defaultReaderType
28884                 });
28885             }
28886             me.reader = Ext.createByAlias('reader.' + reader.type, reader);
28887         }
28888         return me.reader || null;
28889     }
28890 });
28891
28892 /**
28893  * @author Ed Spencer
28894  * @class Ext.ModelManager
28895  * @extends Ext.AbstractManager
28896
28897 The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
28898
28899 __Creating Model Instances__
28900
28901 Model instances can be created by using the {@link Ext#create Ext.create} method. Ext.create replaces
28902 the deprecated {@link #create Ext.ModelManager.create} method. It is also possible to create a model instance
28903 this by using the Model type directly. The following 3 snippets are equivalent:
28904
28905     Ext.define('User', {
28906         extend: 'Ext.data.Model',
28907         fields: ['first', 'last']
28908     });
28909
28910     // method 1, create using Ext.create (recommended)
28911     Ext.create('User', {
28912         first: 'Ed',
28913         last: 'Spencer'
28914     });
28915
28916     // method 2, create through the manager (deprecated)
28917     Ext.ModelManager.create({
28918         first: 'Ed',
28919         last: 'Spencer'
28920     }, 'User');
28921
28922     // method 3, create on the type directly
28923     new User({
28924         first: 'Ed',
28925         last: 'Spencer'
28926     });
28927
28928 __Accessing Model Types__
28929
28930 A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
28931 are normal classes, you can access the type directly. The following snippets are equivalent:
28932
28933     Ext.define('User', {
28934         extend: 'Ext.data.Model',
28935         fields: ['first', 'last']
28936     });
28937
28938     // method 1, access model type through the manager
28939     var UserType = Ext.ModelManager.getModel('User');
28940
28941     // method 2, reference the type directly
28942     var UserType = User;
28943
28944  * @markdown
28945  * @singleton
28946  */
28947 Ext.define('Ext.ModelManager', {
28948     extend: 'Ext.AbstractManager',
28949     alternateClassName: 'Ext.ModelMgr',
28950     requires: ['Ext.data.Association'],
28951
28952     singleton: true,
28953
28954     typeName: 'mtype',
28955
28956     /**
28957      * Private stack of associations that must be created once their associated model has been defined
28958      * @property {Ext.data.Association[]} associationStack
28959      */
28960     associationStack: [],
28961
28962     /**
28963      * Registers a model definition. All model plugins marked with isDefault: true are bootstrapped
28964      * immediately, as are any addition plugins defined in the model config.
28965      * @private
28966      */
28967     registerType: function(name, config) {
28968         var proto = config.prototype,
28969             model;
28970         if (proto && proto.isModel) {
28971             // registering an already defined model
28972             model = config;
28973         } else {
28974             // passing in a configuration
28975             if (!config.extend) {
28976                 config.extend = 'Ext.data.Model';
28977             }
28978             model = Ext.define(name, config);
28979         }
28980         this.types[name] = model;
28981         return model;
28982     },
28983
28984     /**
28985      * @private
28986      * Private callback called whenever a model has just been defined. This sets up any associations
28987      * that were waiting for the given model to be defined
28988      * @param {Function} model The model that was just created
28989      */
28990     onModelDefined: function(model) {
28991         var stack  = this.associationStack,
28992             length = stack.length,
28993             create = [],
28994             association, i, created;
28995
28996         for (i = 0; i < length; i++) {
28997             association = stack[i];
28998
28999             if (association.associatedModel == model.modelName) {
29000                 create.push(association);
29001             }
29002         }
29003
29004         for (i = 0, length = create.length; i < length; i++) {
29005             created = create[i];
29006             this.types[created.ownerModel].prototype.associations.add(Ext.data.Association.create(created));
29007             Ext.Array.remove(stack, created);
29008         }
29009     },
29010
29011     /**
29012      * Registers an association where one of the models defined doesn't exist yet.
29013      * The ModelManager will check when new models are registered if it can link them
29014      * together
29015      * @private
29016      * @param {Ext.data.Association} association The association
29017      */
29018     registerDeferredAssociation: function(association){
29019         this.associationStack.push(association);
29020     },
29021
29022     /**
29023      * Returns the {@link Ext.data.Model} for a given model name
29024      * @param {String/Object} id The id of the model or the model instance.
29025      * @return {Ext.data.Model} a model class.
29026      */
29027     getModel: function(id) {
29028         var model = id;
29029         if (typeof model == 'string') {
29030             model = this.types[model];
29031         }
29032         return model;
29033     },
29034
29035     /**
29036      * Creates a new instance of a Model using the given data.
29037      *
29038      * This method is deprecated.  Use {@link Ext#create Ext.create} instead.  For example:
29039      *
29040      *     Ext.create('User', {
29041      *         first: 'Ed',
29042      *         last: 'Spencer'
29043      *     });
29044      *
29045      * @param {Object} data Data to initialize the Model's fields with
29046      * @param {String} name The name of the model to create
29047      * @param {Number} id (Optional) unique id of the Model instance (see {@link Ext.data.Model})
29048      */
29049     create: function(config, name, id) {
29050         var con = typeof name == 'function' ? name : this.types[name || config.name];
29051
29052         return new con(config, id);
29053     }
29054 }, function() {
29055
29056     /**
29057      * Old way for creating Model classes.  Instead use:
29058      *
29059      *     Ext.define("MyModel", {
29060      *         extend: "Ext.data.Model",
29061      *         fields: []
29062      *     });
29063      *
29064      * @param {String} name Name of the Model class.
29065      * @param {Object} config A configuration object for the Model you wish to create.
29066      * @return {Ext.data.Model} The newly registered Model
29067      * @member Ext
29068      * @deprecated 4.0.0 Use {@link Ext#define} instead.
29069      */
29070     Ext.regModel = function() {
29071         return this.ModelManager.registerType.apply(this.ModelManager, arguments);
29072     };
29073 });
29074
29075 /**
29076  * @singleton
29077  *
29078  * Provides a registry of available Plugin classes indexed by a mnemonic code known as the Plugin's ptype.
29079  *
29080  * A plugin may be specified simply as a *config object* as long as the correct `ptype` is specified:
29081  *
29082  *     {
29083  *         ptype: 'gridviewdragdrop',
29084  *         dragText: 'Drag and drop to reorganize'
29085  *     }
29086  *
29087  * Or just use the ptype on its own:
29088  *
29089  *     'gridviewdragdrop'
29090  *
29091  * Alternatively you can instantiate the plugin with Ext.create:
29092  *
29093  *     Ext.create('Ext.view.plugin.AutoComplete', {
29094  *         ptype: 'gridviewdragdrop',
29095  *         dragText: 'Drag and drop to reorganize'
29096  *     })
29097  */
29098 Ext.define('Ext.PluginManager', {
29099     extend: 'Ext.AbstractManager',
29100     alternateClassName: 'Ext.PluginMgr',
29101     singleton: true,
29102     typeName: 'ptype',
29103
29104     /**
29105      * Creates a new Plugin from the specified config object using the config object's ptype to determine the class to
29106      * instantiate.
29107      * @param {Object} config A configuration object for the Plugin you wish to create.
29108      * @param {Function} defaultType (optional) The constructor to provide the default Plugin type if the config object does not
29109      * contain a `ptype`. (Optional if the config contains a `ptype`).
29110      * @return {Ext.Component} The newly instantiated Plugin.
29111      */
29112     //create: function(plugin, defaultType) {
29113     //    if (plugin instanceof this) {
29114     //        return plugin;
29115     //    } else {
29116     //        var type, config = {};
29117     //
29118     //        if (Ext.isString(plugin)) {
29119     //            type = plugin;
29120     //        }
29121     //        else {
29122     //            type = plugin[this.typeName] || defaultType;
29123     //            config = plugin;
29124     //        }
29125     //
29126     //        return Ext.createByAlias('plugin.' + type, config);
29127     //    }
29128     //},
29129
29130     create : function(config, defaultType){
29131         if (config.init) {
29132             return config;
29133         } else {
29134             return Ext.createByAlias('plugin.' + (config.ptype || defaultType), config);
29135         }
29136
29137         // Prior system supported Singleton plugins.
29138         //var PluginCls = this.types[config.ptype || defaultType];
29139         //if (PluginCls.init) {
29140         //    return PluginCls;
29141         //} else {
29142         //    return new PluginCls(config);
29143         //}
29144     },
29145
29146     /**
29147      * Returns all plugins registered with the given type. Here, 'type' refers to the type of plugin, not its ptype.
29148      * @param {String} type The type to search for
29149      * @param {Boolean} defaultsOnly True to only return plugins of this type where the plugin's isDefault property is
29150      * truthy
29151      * @return {Ext.AbstractPlugin[]} All matching plugins
29152      */
29153     findByType: function(type, defaultsOnly) {
29154         var matches = [],
29155             types   = this.types;
29156
29157         for (var name in types) {
29158             if (!types.hasOwnProperty(name)) {
29159                 continue;
29160             }
29161             var item = types[name];
29162
29163             if (item.type == type && (!defaultsOnly || (defaultsOnly === true && item.isDefault))) {
29164                 matches.push(item);
29165             }
29166         }
29167
29168         return matches;
29169     }
29170 }, function() {
29171     /**
29172      * Shorthand for {@link Ext.PluginManager#registerType}
29173      * @param {String} ptype The ptype mnemonic string by which the Plugin class
29174      * may be looked up.
29175      * @param {Function} cls The new Plugin class.
29176      * @member Ext
29177      * @method preg
29178      */
29179     Ext.preg = function() {
29180         return Ext.PluginManager.registerType.apply(Ext.PluginManager, arguments);
29181     };
29182 });
29183
29184 /**
29185  * Represents an HTML fragment template. Templates may be {@link #compile precompiled} for greater performance.
29186  *
29187  * An instance of this class may be created by passing to the constructor either a single argument, or multiple
29188  * arguments:
29189  *
29190  * # Single argument: String/Array
29191  *
29192  * The single argument may be either a String or an Array:
29193  *
29194  * - String:
29195  *
29196  *       var t = new Ext.Template("<div>Hello {0}.</div>");
29197  *       t.{@link #append}('some-element', ['foo']);
29198  *
29199  * - Array:
29200  *
29201  *   An Array will be combined with `join('')`.
29202  *
29203  *       var t = new Ext.Template([
29204  *           '<div name="{id}">',
29205  *               '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
29206  *           '</div>',
29207  *       ]);
29208  *       t.{@link #compile}();
29209  *       t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
29210  *
29211  * # Multiple arguments: String, Object, Array, ...
29212  *
29213  * Multiple arguments will be combined with `join('')`.
29214  *
29215  *     var t = new Ext.Template(
29216  *         '<div name="{id}">',
29217  *             '<span class="{cls}">{name} {value}</span>',
29218  *         '</div>',
29219  *         // a configuration object:
29220  *         {
29221  *             compiled: true,      // {@link #compile} immediately
29222  *         }
29223  *     );
29224  *
29225  * # Notes
29226  *
29227  * - For a list of available format functions, see {@link Ext.util.Format}.
29228  * - `disableFormats` reduces `{@link #apply}` time when no formatting is required.
29229  */
29230 Ext.define('Ext.Template', {
29231
29232     /* Begin Definitions */
29233
29234     requires: ['Ext.DomHelper', 'Ext.util.Format'],
29235
29236     inheritableStatics: {
29237         /**
29238          * Creates a template from the passed element's value (_display:none_ textarea, preferred) or innerHTML.
29239          * @param {String/HTMLElement} el A DOM element or its id
29240          * @param {Object} config (optional) Config object
29241          * @return {Ext.Template} The created template
29242          * @static
29243          * @inheritable
29244          */
29245         from: function(el, config) {
29246             el = Ext.getDom(el);
29247             return new this(el.value || el.innerHTML, config || '');
29248         }
29249     },
29250
29251     /* End Definitions */
29252
29253     /**
29254      * Creates new template.
29255      * 
29256      * @param {String...} html List of strings to be concatenated into template.
29257      * Alternatively an array of strings can be given, but then no config object may be passed.
29258      * @param {Object} config (optional) Config object
29259      */
29260     constructor: function(html) {
29261         var me = this,
29262             args = arguments,
29263             buffer = [],
29264             i = 0,
29265             length = args.length,
29266             value;
29267
29268         me.initialConfig = {};
29269
29270         if (length > 1) {
29271             for (; i < length; i++) {
29272                 value = args[i];
29273                 if (typeof value == 'object') {
29274                     Ext.apply(me.initialConfig, value);
29275                     Ext.apply(me, value);
29276                 } else {
29277                     buffer.push(value);
29278                 }
29279             }
29280             html = buffer.join('');
29281         } else {
29282             if (Ext.isArray(html)) {
29283                 buffer.push(html.join(''));
29284             } else {
29285                 buffer.push(html);
29286             }
29287         }
29288
29289         // @private
29290         me.html = buffer.join('');
29291
29292         if (me.compiled) {
29293             me.compile();
29294         }
29295     },
29296
29297     isTemplate: true,
29298
29299     /**
29300      * @cfg {Boolean} compiled
29301      * True to immediately compile the template. Defaults to false.
29302      */
29303
29304     /**
29305      * @cfg {Boolean} disableFormats
29306      * True to disable format functions in the template. If the template doesn't contain
29307      * format functions, setting disableFormats to true will reduce apply time. Defaults to false.
29308      */
29309     disableFormats: false,
29310
29311     re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
29312
29313     /**
29314      * Returns an HTML fragment of this template with the specified values applied.
29315      *
29316      * @param {Object/Array} values The template values. Can be an array if your params are numeric:
29317      *
29318      *     var tpl = new Ext.Template('Name: {0}, Age: {1}');
29319      *     tpl.applyTemplate(['John', 25]);
29320      *
29321      * or an object:
29322      *
29323      *     var tpl = new Ext.Template('Name: {name}, Age: {age}');
29324      *     tpl.applyTemplate({name: 'John', age: 25});
29325      *
29326      * @return {String} The HTML fragment
29327      */
29328     applyTemplate: function(values) {
29329         var me = this,
29330             useFormat = me.disableFormats !== true,
29331             fm = Ext.util.Format,
29332             tpl = me;
29333
29334         if (me.compiled) {
29335             return me.compiled(values);
29336         }
29337         function fn(m, name, format, args) {
29338             if (format && useFormat) {
29339                 if (args) {
29340                     args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
29341                 } else {
29342                     args = [values[name]];
29343                 }
29344                 if (format.substr(0, 5) == "this.") {
29345                     return tpl[format.substr(5)].apply(tpl, args);
29346                 }
29347                 else {
29348                     return fm[format].apply(fm, args);
29349                 }
29350             }
29351             else {
29352                 return values[name] !== undefined ? values[name] : "";
29353             }
29354         }
29355         return me.html.replace(me.re, fn);
29356     },
29357
29358     /**
29359      * Sets the HTML used as the template and optionally compiles it.
29360      * @param {String} html
29361      * @param {Boolean} compile (optional) True to compile the template.
29362      * @return {Ext.Template} this
29363      */
29364     set: function(html, compile) {
29365         var me = this;
29366         me.html = html;
29367         me.compiled = null;
29368         return compile ? me.compile() : me;
29369     },
29370
29371     compileARe: /\\/g,
29372     compileBRe: /(\r\n|\n)/g,
29373     compileCRe: /'/g,
29374
29375     /**
29376      * Compiles the template into an internal function, eliminating the RegEx overhead.
29377      * @return {Ext.Template} this
29378      */
29379     compile: function() {
29380         var me = this,
29381             fm = Ext.util.Format,
29382             useFormat = me.disableFormats !== true,
29383             body, bodyReturn;
29384
29385         function fn(m, name, format, args) {
29386             if (format && useFormat) {
29387                 args = args ? ',' + args: "";
29388                 if (format.substr(0, 5) != "this.") {
29389                     format = "fm." + format + '(';
29390                 }
29391                 else {
29392                     format = 'this.' + format.substr(5) + '(';
29393                 }
29394             }
29395             else {
29396                 args = '';
29397                 format = "(values['" + name + "'] == undefined ? '' : ";
29398             }
29399             return "'," + format + "values['" + name + "']" + args + ") ,'";
29400         }
29401
29402         bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
29403         body = "this.compiled = function(values){ return ['" + bodyReturn + "'].join('');};";
29404         eval(body);
29405         return me;
29406     },
29407
29408     /**
29409      * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
29410      *
29411      * @param {String/HTMLElement/Ext.Element} el The context element
29412      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29413      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
29414      * @return {HTMLElement/Ext.Element} The new node or Element
29415      */
29416     insertFirst: function(el, values, returnElement) {
29417         return this.doInsert('afterBegin', el, values, returnElement);
29418     },
29419
29420     /**
29421      * Applies the supplied values to the template and inserts the new node(s) before el.
29422      *
29423      * @param {String/HTMLElement/Ext.Element} el The context element
29424      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29425      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
29426      * @return {HTMLElement/Ext.Element} The new node or Element
29427      */
29428     insertBefore: function(el, values, returnElement) {
29429         return this.doInsert('beforeBegin', el, values, returnElement);
29430     },
29431
29432     /**
29433      * Applies the supplied values to the template and inserts the new node(s) after el.
29434      *
29435      * @param {String/HTMLElement/Ext.Element} el The context element
29436      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29437      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
29438      * @return {HTMLElement/Ext.Element} The new node or Element
29439      */
29440     insertAfter: function(el, values, returnElement) {
29441         return this.doInsert('afterEnd', el, values, returnElement);
29442     },
29443
29444     /**
29445      * Applies the supplied `values` to the template and appends the new node(s) to the specified `el`.
29446      *
29447      * For example usage see {@link Ext.Template Ext.Template class docs}.
29448      *
29449      * @param {String/HTMLElement/Ext.Element} el The context element
29450      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29451      * @param {Boolean} returnElement (optional) true to return an Ext.Element.
29452      * @return {HTMLElement/Ext.Element} The new node or Element
29453      */
29454     append: function(el, values, returnElement) {
29455         return this.doInsert('beforeEnd', el, values, returnElement);
29456     },
29457
29458     doInsert: function(where, el, values, returnEl) {
29459         el = Ext.getDom(el);
29460         var newNode = Ext.DomHelper.insertHtml(where, el, this.applyTemplate(values));
29461         return returnEl ? Ext.get(newNode, true) : newNode;
29462     },
29463
29464     /**
29465      * Applies the supplied values to the template and overwrites the content of el with the new node(s).
29466      *
29467      * @param {String/HTMLElement/Ext.Element} el The context element
29468      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
29469      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
29470      * @return {HTMLElement/Ext.Element} The new node or Element
29471      */
29472     overwrite: function(el, values, returnElement) {
29473         el = Ext.getDom(el);
29474         el.innerHTML = this.applyTemplate(values);
29475         return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
29476     }
29477 }, function() {
29478
29479     /**
29480      * @method apply
29481      * @member Ext.Template
29482      * Alias for {@link #applyTemplate}.
29483      * @alias Ext.Template#applyTemplate
29484      */
29485     this.createAlias('apply', 'applyTemplate');
29486 });
29487
29488 /**
29489  * A template class that supports advanced functionality like:
29490  *
29491  * - Autofilling arrays using templates and sub-templates
29492  * - Conditional processing with basic comparison operators
29493  * - Basic math function support
29494  * - Execute arbitrary inline code with special built-in template variables
29495  * - Custom member functions
29496  * - 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
29497  *
29498  * XTemplate provides the templating mechanism built into:
29499  *
29500  * - {@link Ext.view.View}
29501  *
29502  * The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
29503  * demonstrate all of the supported features.
29504  *
29505  * # Sample Data
29506  *
29507  * This is the data object used for reference in each code example:
29508  *
29509  *     var data = {
29510  *         name: 'Tommy Maintz',
29511  *         title: 'Lead Developer',
29512  *         company: 'Sencha Inc.',
29513  *         email: 'tommy@sencha.com',
29514  *         address: '5 Cups Drive',
29515  *         city: 'Palo Alto',
29516  *         state: 'CA',
29517  *         zip: '44102',
29518  *         drinks: ['Coffee', 'Soda', 'Water'],
29519  *         kids: [
29520  *             {
29521  *                 name: 'Joshua',
29522  *                 age:3
29523  *             },
29524  *             {
29525  *                 name: 'Matthew',
29526  *                 age:2
29527  *             },
29528  *             {
29529  *                 name: 'Solomon',
29530  *                 age:0
29531  *             }
29532  *         ]
29533  *     };
29534  *
29535  * # Auto filling of arrays
29536  *
29537  * The **tpl** tag and the **for** operator are used to process the provided data object:
29538  *
29539  * - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
29540  *   tag for each item in the array.
29541  * - If for="." is specified, the data object provided is examined.
29542  * - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
29543  *
29544  * Examples:
29545  *
29546  *     <tpl for=".">...</tpl>       // loop through array at root node
29547  *     <tpl for="foo">...</tpl>     // loop through array at foo node
29548  *     <tpl for="foo.bar">...</tpl> // loop through array at foo.bar node
29549  *
29550  * Using the sample data above:
29551  *
29552  *     var tpl = new Ext.XTemplate(
29553  *         '<p>Kids: ',
29554  *         '<tpl for=".">',       // process the data.kids node
29555  *             '<p>{#}. {name}</p>',  // use current array index to autonumber
29556  *         '</tpl></p>'
29557  *     );
29558  *     tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
29559  *
29560  * An example illustrating how the **for** property can be leveraged to access specified members of the provided data
29561  * object to populate the template:
29562  *
29563  *     var tpl = new Ext.XTemplate(
29564  *         '<p>Name: {name}</p>',
29565  *         '<p>Title: {title}</p>',
29566  *         '<p>Company: {company}</p>',
29567  *         '<p>Kids: ',
29568  *         '<tpl for="kids">',     // interrogate the kids property within the data
29569  *             '<p>{name}</p>',
29570  *         '</tpl></p>'
29571  *     );
29572  *     tpl.overwrite(panel.body, data);  // pass the root node of the data object
29573  *
29574  * Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
29575  * loop. This variable will represent the value of the array at the current index:
29576  *
29577  *     var tpl = new Ext.XTemplate(
29578  *         '<p>{name}\'s favorite beverages:</p>',
29579  *         '<tpl for="drinks">',
29580  *             '<div> - {.}</div>',
29581  *         '</tpl>'
29582  *     );
29583  *     tpl.overwrite(panel.body, data);
29584  *
29585  * When processing a sub-template, for example while looping through a child array, you can access the parent object's
29586  * members via the **parent** object:
29587  *
29588  *     var tpl = new Ext.XTemplate(
29589  *         '<p>Name: {name}</p>',
29590  *         '<p>Kids: ',
29591  *         '<tpl for="kids">',
29592  *             '<tpl if="age &gt; 1">',
29593  *                 '<p>{name}</p>',
29594  *                 '<p>Dad: {parent.name}</p>',
29595  *             '</tpl>',
29596  *         '</tpl></p>'
29597  *     );
29598  *     tpl.overwrite(panel.body, data);
29599  *
29600  * # Conditional processing with basic comparison operators
29601  *
29602  * The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
29603  * specific parts of the template. Notes:
29604  *
29605  * - Double quotes must be encoded if used within the conditional
29606  * - There is no else operator -- if needed, two opposite if statements should be used.
29607  *
29608  * Examples:
29609  *
29610  *     <tpl if="age > 1 && age < 10">Child</tpl>
29611  *     <tpl if="age >= 10 && age < 18">Teenager</tpl>
29612  *     <tpl if="this.isGirl(name)">...</tpl>
29613  *     <tpl if="id==\'download\'">...</tpl>
29614  *     <tpl if="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
29615  *     // no good:
29616  *     <tpl if="name == "Tommy"">Hello</tpl>
29617  *     // encode " if it is part of the condition, e.g.
29618  *     <tpl if="name == &quot;Tommy&quot;">Hello</tpl>
29619  *
29620  * Using the sample data above:
29621  *
29622  *     var tpl = new Ext.XTemplate(
29623  *         '<p>Name: {name}</p>',
29624  *         '<p>Kids: ',
29625  *         '<tpl for="kids">',
29626  *             '<tpl if="age &gt; 1">',
29627  *                 '<p>{name}</p>',
29628  *             '</tpl>',
29629  *         '</tpl></p>'
29630  *     );
29631  *     tpl.overwrite(panel.body, data);
29632  *
29633  * # Basic math support
29634  *
29635  * The following basic math operators may be applied directly on numeric data values:
29636  *
29637  *     + - * /
29638  *
29639  * For example:
29640  *
29641  *     var tpl = new Ext.XTemplate(
29642  *         '<p>Name: {name}</p>',
29643  *         '<p>Kids: ',
29644  *         '<tpl for="kids">',
29645  *             '<tpl if="age &gt; 1">',  // <-- Note that the > is encoded
29646  *                 '<p>{#}: {name}</p>',  // <-- Auto-number each item
29647  *                 '<p>In 5 Years: {age+5}</p>',  // <-- Basic math
29648  *                 '<p>Dad: {parent.name}</p>',
29649  *             '</tpl>',
29650  *         '</tpl></p>'
29651  *     );
29652  *     tpl.overwrite(panel.body, data);
29653  *
29654  * # Execute arbitrary inline code with special built-in template variables
29655  *
29656  * Anything between `{[ ... ]}` is considered code to be executed in the scope of the template. There are some special
29657  * variables available in that code:
29658  *
29659  * - **values**: The values in the current scope. If you are using scope changing sub-templates,
29660  *   you can change what values is.
29661  * - **parent**: The scope (values) of the ancestor template.
29662  * - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
29663  * - **xcount**: If you are in a looping template, the total length of the array you are looping.
29664  *
29665  * This example demonstrates basic row striping using an inline code block and the xindex variable:
29666  *
29667  *     var tpl = new Ext.XTemplate(
29668  *         '<p>Name: {name}</p>',
29669  *         '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
29670  *         '<p>Kids: ',
29671  *         '<tpl for="kids">',
29672  *             '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
29673  *             '{name}',
29674  *             '</div>',
29675  *         '</tpl></p>'
29676  *      );
29677  *     tpl.overwrite(panel.body, data);
29678  *
29679  * # Template member functions
29680  *
29681  * One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
29682  * more complex processing:
29683  *
29684  *     var tpl = new Ext.XTemplate(
29685  *         '<p>Name: {name}</p>',
29686  *         '<p>Kids: ',
29687  *         '<tpl for="kids">',
29688  *             '<tpl if="this.isGirl(name)">',
29689  *                 '<p>Girl: {name} - {age}</p>',
29690  *             '</tpl>',
29691  *              // use opposite if statement to simulate 'else' processing:
29692  *             '<tpl if="this.isGirl(name) == false">',
29693  *                 '<p>Boy: {name} - {age}</p>',
29694  *             '</tpl>',
29695  *             '<tpl if="this.isBaby(age)">',
29696  *                 '<p>{name} is a baby!</p>',
29697  *             '</tpl>',
29698  *         '</tpl></p>',
29699  *         {
29700  *             // XTemplate configuration:
29701  *             disableFormats: true,
29702  *             // member functions:
29703  *             isGirl: function(name){
29704  *                return name == 'Sara Grace';
29705  *             },
29706  *             isBaby: function(age){
29707  *                return age < 1;
29708  *             }
29709  *         }
29710  *     );
29711  *     tpl.overwrite(panel.body, data);
29712  */
29713 Ext.define('Ext.XTemplate', {
29714
29715     /* Begin Definitions */
29716
29717     extend: 'Ext.Template',
29718
29719     /* End Definitions */
29720
29721     argsRe: /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
29722     nameRe: /^<tpl\b[^>]*?for="(.*?)"/,
29723     ifRe: /^<tpl\b[^>]*?if="(.*?)"/,
29724     execRe: /^<tpl\b[^>]*?exec="(.*?)"/,
29725     constructor: function() {
29726         this.callParent(arguments);
29727
29728         var me = this,
29729             html = me.html,
29730             argsRe = me.argsRe,
29731             nameRe = me.nameRe,
29732             ifRe = me.ifRe,
29733             execRe = me.execRe,
29734             id = 0,
29735             tpls = [],
29736             VALUES = 'values',
29737             PARENT = 'parent',
29738             XINDEX = 'xindex',
29739             XCOUNT = 'xcount',
29740             RETURN = 'return ',
29741             WITHVALUES = 'with(values){ ',
29742             m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
29743
29744         html = ['<tpl>', html, '</tpl>'].join('');
29745
29746         while ((m = html.match(argsRe))) {
29747             exp = null;
29748             fn = null;
29749             exec = null;
29750             matchName = m[0].match(nameRe);
29751             matchIf = m[0].match(ifRe);
29752             matchExec = m[0].match(execRe);
29753
29754             exp = matchIf ? matchIf[1] : null;
29755             if (exp) {
29756                 fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
29757             }
29758
29759             exp = matchExec ? matchExec[1] : null;
29760             if (exp) {
29761                 exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
29762             }
29763
29764             name = matchName ? matchName[1] : null;
29765             if (name) {
29766                 if (name === '.') {
29767                     name = VALUES;
29768                 } else if (name === '..') {
29769                     name = PARENT;
29770                 }
29771                 name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
29772             }
29773
29774             tpls.push({
29775                 id: id,
29776                 target: name,
29777                 exec: exec,
29778                 test: fn,
29779                 body: m[1] || ''
29780             });
29781
29782             html = html.replace(m[0], '{xtpl' + id + '}');
29783             id = id + 1;
29784         }
29785
29786         for (i = tpls.length - 1; i >= 0; --i) {
29787             me.compileTpl(tpls[i]);
29788         }
29789         me.master = tpls[tpls.length - 1];
29790         me.tpls = tpls;
29791     },
29792
29793     // @private
29794     applySubTemplate: function(id, values, parent, xindex, xcount) {
29795         var me = this, t = me.tpls[id];
29796         return t.compiled.call(me, values, parent, xindex, xcount);
29797     },
29798
29799     /**
29800      * @cfg {RegExp} codeRe
29801      * The regular expression used to match code variables. Default: matches {[expression]}.
29802      */
29803     codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
29804
29805     /**
29806      * @cfg {Boolean} compiled
29807      * Only applies to {@link Ext.Template}, XTemplates are compiled automatically.
29808      */
29809
29810     re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
29811
29812     // @private
29813     compileTpl: function(tpl) {
29814         var fm = Ext.util.Format,
29815             me = this,
29816             useFormat = me.disableFormats !== true,
29817             body, bodyReturn, evaluatedFn;
29818
29819         function fn(m, name, format, args, math) {
29820             var v;
29821             // name is what is inside the {}
29822             // Name begins with xtpl, use a Sub Template
29823             if (name.substr(0, 4) == 'xtpl') {
29824                 return "',this.applySubTemplate(" + name.substr(4) + ", values, parent, xindex, xcount),'";
29825             }
29826             // name = "." - Just use the values object.
29827             if (name == '.') {
29828                 // filter to not include arrays/objects/nulls
29829                 v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
29830             }
29831
29832             // name = "#" - Use the xindex
29833             else if (name == '#') {
29834                 v = 'xindex';
29835             }
29836             else if (name.substr(0, 7) == "parent.") {
29837                 v = name;
29838             }
29839             // name has a . in it - Use object literal notation, starting from values
29840             else if (name.indexOf('.') != -1) {
29841                 v = "values." + name;
29842             }
29843
29844             // name is a property of values
29845             else {
29846                 v = "values['" + name + "']";
29847             }
29848             if (math) {
29849                 v = '(' + v + math + ')';
29850             }
29851             if (format && useFormat) {
29852                 args = args ? ',' + args : "";
29853                 if (format.substr(0, 5) != "this.") {
29854                     format = "fm." + format + '(';
29855                 }
29856                 else {
29857                     format = 'this.' + format.substr(5) + '(';
29858                 }
29859             }
29860             else {
29861                 args = '';
29862                 format = "(" + v + " === undefined ? '' : ";
29863             }
29864             return "'," + format + v + args + "),'";
29865         }
29866
29867         function codeFn(m, code) {
29868             // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
29869             return "',(" + code.replace(me.compileARe, "'") + "),'";
29870         }
29871
29872         bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn).replace(me.codeRe, codeFn);
29873         body = "evaluatedFn = function(values, parent, xindex, xcount){return ['" + bodyReturn + "'].join('');};";
29874         eval(body);
29875
29876         tpl.compiled = function(values, parent, xindex, xcount) {
29877             var vs,
29878                 length,
29879                 buffer,
29880                 i;
29881
29882             if (tpl.test && !tpl.test.call(me, values, parent, xindex, xcount)) {
29883                 return '';
29884             }
29885
29886             vs = tpl.target ? tpl.target.call(me, values, parent) : values;
29887             if (!vs) {
29888                return '';
29889             }
29890
29891             parent = tpl.target ? values : parent;
29892             if (tpl.target && Ext.isArray(vs)) {
29893                 buffer = [];
29894                 length = vs.length;
29895                 if (tpl.exec) {
29896                     for (i = 0; i < length; i++) {
29897                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
29898                         tpl.exec.call(me, vs[i], parent, i + 1, length);
29899                     }
29900                 } else {
29901                     for (i = 0; i < length; i++) {
29902                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
29903                     }
29904                 }
29905                 return buffer.join('');
29906             }
29907
29908             if (tpl.exec) {
29909                 tpl.exec.call(me, vs, parent, xindex, xcount);
29910             }
29911             return evaluatedFn.call(me, vs, parent, xindex, xcount);
29912         };
29913
29914         return this;
29915     },
29916
29917     // inherit docs from Ext.Template
29918     applyTemplate: function(values) {
29919         return this.master.compiled.call(this, values, {}, 1, 1);
29920     },
29921
29922     /**
29923      * Does nothing. XTemplates are compiled automatically, so this function simply returns this.
29924      * @return {Ext.XTemplate} this
29925      */
29926     compile: function() {
29927         return this;
29928     }
29929 }, function() {
29930     // re-create the alias, inheriting it from Ext.Template doesn't work as intended.
29931     this.createAlias('apply', 'applyTemplate');
29932 });
29933
29934 /**
29935  * @class Ext.app.Controller
29936  *
29937  * Controllers are the glue that binds an application together. All they really do is listen for events (usually from
29938  * views) and take some action. Here's how we might create a Controller to manage Users:
29939  *
29940  *     Ext.define('MyApp.controller.Users', {
29941  *         extend: 'Ext.app.Controller',
29942  *
29943  *         init: function() {
29944  *             console.log('Initialized Users! This happens before the Application launch function is called');
29945  *         }
29946  *     });
29947  *
29948  * The init function is a special method that is called when your application boots. It is called before the
29949  * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
29950  * your Viewport is created.
29951  *
29952  * The init function is a great place to set up how your controller interacts with the view, and is usually used in
29953  * conjunction with another Controller function - {@link Ext.app.Controller#control control}. The control function
29954  * makes it easy to listen to events on your view classes and take some action with a handler function. Let's update
29955  * our Users controller to tell us when the panel is rendered:
29956  *
29957  *     Ext.define('MyApp.controller.Users', {
29958  *         extend: 'Ext.app.Controller',
29959  *
29960  *         init: function() {
29961  *             this.control({
29962  *                 'viewport > panel': {
29963  *                     render: this.onPanelRendered
29964  *                 }
29965  *             });
29966  *         },
29967  *
29968  *         onPanelRendered: function() {
29969  *             console.log('The panel was rendered');
29970  *         }
29971  *     });
29972  *
29973  * We've updated the init function to use this.control to set up listeners on views in our application. The control
29974  * function uses the new ComponentQuery engine to quickly and easily get references to components on the page. If you
29975  * are not familiar with ComponentQuery yet, be sure to check out the {@link Ext.ComponentQuery documentation}. In brief though,
29976  * it allows us to pass a CSS-like selector that will find every matching component on the page.
29977  *
29978  * In our init function above we supplied 'viewport > panel', which translates to "find me every Panel that is a direct
29979  * child of a Viewport". We then supplied an object that maps event names (just 'render' in this case) to handler
29980  * functions. The overall effect is that whenever any component that matches our selector fires a 'render' event, our
29981  * onPanelRendered function is called.
29982  *
29983  * <u>Using refs</u>
29984  *
29985  * One of the most useful parts of Controllers is the new ref system. These use the new {@link Ext.ComponentQuery} to
29986  * make it really easy to get references to Views on your page. Let's look at an example of this now:
29987  *
29988  *     Ext.define('MyApp.controller.Users', {
29989  *         extend: 'Ext.app.Controller',
29990  *
29991  *         refs: [
29992  *             {
29993  *                 ref: 'list',
29994  *                 selector: 'grid'
29995  *             }
29996  *         ],
29997  *
29998  *         init: function() {
29999  *             this.control({
30000  *                 'button': {
30001  *                     click: this.refreshGrid
30002  *                 }
30003  *             });
30004  *         },
30005  *
30006  *         refreshGrid: function() {
30007  *             this.getList().store.load();
30008  *         }
30009  *     });
30010  *
30011  * This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which contains a single button to
30012  * refresh the Grid when clicked. In our refs array, we set up a reference to the grid. There are two parts to this -
30013  * the 'selector', which is a {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and
30014  * assigns it to the reference 'list'.
30015  *
30016  * By giving the reference a name, we get a number of things for free. The first is the getList function that we use in
30017  * the refreshGrid method above. This is generated automatically by the Controller based on the name of our ref, which
30018  * was capitalized and prepended with get to go from 'list' to 'getList'.
30019  *
30020  * The way this works is that the first time getList is called by your code, the ComponentQuery selector is run and the
30021  * first component that matches the selector ('grid' in this case) will be returned. All future calls to getList will
30022  * use a cached reference to that grid. Usually it is advised to use a specific ComponentQuery selector that will only
30023  * match a single View in your application (in the case above our selector will match any grid on the page).
30024  *
30025  * Bringing it all together, our init function is called when the application boots, at which time we call this.control
30026  * to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid function (again, this will
30027  * match any button on the page so we advise a more specific selector than just 'button', but have left it this way for
30028  * simplicity). When the button is clicked we use out getList function to refresh the grid.
30029  *
30030  * You can create any number of refs and control any number of components this way, simply adding more functions to
30031  * your Controller as you go. For an example of real-world usage of Controllers see the Feed Viewer example in the
30032  * examples/app/feed-viewer folder in the SDK download.
30033  *
30034  * <u>Generated getter methods</u>
30035  *
30036  * Refs aren't the only thing that generate convenient getter methods. Controllers often have to deal with Models and
30037  * Stores so the framework offers a couple of easy ways to get access to those too. Let's look at another example:
30038  *
30039  *     Ext.define('MyApp.controller.Users', {
30040  *         extend: 'Ext.app.Controller',
30041  *
30042  *         models: ['User'],
30043  *         stores: ['AllUsers', 'AdminUsers'],
30044  *
30045  *         init: function() {
30046  *             var User = this.getUserModel(),
30047  *                 allUsers = this.getAllUsersStore();
30048  *
30049  *             var ed = new User({name: 'Ed'});
30050  *             allUsers.add(ed);
30051  *         }
30052  *     });
30053  *
30054  * By specifying Models and Stores that the Controller cares about, it again dynamically loads them from the appropriate
30055  * locations (app/model/User.js, app/store/AllUsers.js and app/store/AdminUsers.js in this case) and creates getter
30056  * functions for them all. The example above will create a new User model instance and add it to the AllUsers Store.
30057  * Of course, you could do anything in this function but in this case we just did something simple to demonstrate the
30058  * functionality.
30059  *
30060  * <u>Further Reading</u>
30061  *
30062  * For more information about writing Ext JS 4 applications, please see the
30063  * [application architecture guide](#/guide/application_architecture). Also see the {@link Ext.app.Application} documentation.
30064  *
30065  * @docauthor Ed Spencer
30066  */
30067 Ext.define('Ext.app.Controller', {
30068
30069     mixins: {
30070         observable: 'Ext.util.Observable'
30071     },
30072
30073     /**
30074      * @cfg {String} id The id of this controller. You can use this id when dispatching.
30075      */
30076     
30077     /**
30078      * @cfg {String[]} models
30079      * Array of models to require from AppName.model namespace. For example:
30080      * 
30081      *     Ext.define("MyApp.controller.Foo", {
30082      *         extend: "Ext.app.Controller",
30083      *         models: ['User', 'Vehicle']
30084      *     });
30085      * 
30086      * This is equivalent of:
30087      * 
30088      *     Ext.define("MyApp.controller.Foo", {
30089      *         extend: "Ext.app.Controller",
30090      *         requires: ['MyApp.model.User', 'MyApp.model.Vehicle']
30091      *     });
30092      * 
30093      */
30094
30095     /**
30096      * @cfg {String[]} views
30097      * Array of views to require from AppName.view namespace. For example:
30098      * 
30099      *     Ext.define("MyApp.controller.Foo", {
30100      *         extend: "Ext.app.Controller",
30101      *         views: ['List', 'Detail']
30102      *     });
30103      * 
30104      * This is equivalent of:
30105      * 
30106      *     Ext.define("MyApp.controller.Foo", {
30107      *         extend: "Ext.app.Controller",
30108      *         requires: ['MyApp.view.List', 'MyApp.view.Detail']
30109      *     });
30110      * 
30111      */
30112
30113     /**
30114      * @cfg {String[]} stores
30115      * Array of stores to require from AppName.store namespace. For example:
30116      * 
30117      *     Ext.define("MyApp.controller.Foo", {
30118      *         extend: "Ext.app.Controller",
30119      *         stores: ['Users', 'Vehicles']
30120      *     });
30121      * 
30122      * This is equivalent of:
30123      * 
30124      *     Ext.define("MyApp.controller.Foo", {
30125      *         extend: "Ext.app.Controller",
30126      *         requires: ['MyApp.store.Users', 'MyApp.store.Vehicles']
30127      *     });
30128      * 
30129      */
30130
30131     onClassExtended: function(cls, data) {
30132         var className = Ext.getClassName(cls),
30133             match = className.match(/^(.*)\.controller\./);
30134
30135         if (match !== null) {
30136             var namespace = Ext.Loader.getPrefix(className) || match[1],
30137                 onBeforeClassCreated = data.onBeforeClassCreated,
30138                 requires = [],
30139                 modules = ['model', 'view', 'store'],
30140                 prefix;
30141
30142             data.onBeforeClassCreated = function(cls, data) {
30143                 var i, ln, module,
30144                     items, j, subLn, item;
30145
30146                 for (i = 0,ln = modules.length; i < ln; i++) {
30147                     module = modules[i];
30148
30149                     items = Ext.Array.from(data[module + 's']);
30150
30151                     for (j = 0,subLn = items.length; j < subLn; j++) {
30152                         item = items[j];
30153
30154                         prefix = Ext.Loader.getPrefix(item);
30155
30156                         if (prefix === '' || prefix === item) {
30157                             requires.push(namespace + '.' + module + '.' + item);
30158                         }
30159                         else {
30160                             requires.push(item);
30161                         }
30162                     }
30163                 }
30164
30165                 Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
30166             };
30167         }
30168     },
30169
30170     /**
30171      * Creates new Controller.
30172      * @param {Object} config (optional) Config object.
30173      */
30174     constructor: function(config) {
30175         this.mixins.observable.constructor.call(this, config);
30176
30177         Ext.apply(this, config || {});
30178
30179         this.createGetters('model', this.models);
30180         this.createGetters('store', this.stores);
30181         this.createGetters('view', this.views);
30182
30183         if (this.refs) {
30184             this.ref(this.refs);
30185         }
30186     },
30187
30188     /**
30189      * A template method that is called when your application boots. It is called before the
30190      * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
30191      * your Viewport is created.
30192      * 
30193      * @param {Ext.app.Application} application
30194      * @template
30195      */
30196     init: function(application) {},
30197
30198     /**
30199      * A template method like {@link #init}, but called after the viewport is created.
30200      * This is called after the {@link Ext.app.Application#launch launch} method of Application is executed.
30201      * 
30202      * @param {Ext.app.Application} application
30203      * @template
30204      */
30205     onLaunch: function(application) {},
30206
30207     createGetters: function(type, refs) {
30208         type = Ext.String.capitalize(type);
30209         Ext.Array.each(refs, function(ref) {
30210             var fn = 'get',
30211                 parts = ref.split('.');
30212
30213             // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
30214             Ext.Array.each(parts, function(part) {
30215                 fn += Ext.String.capitalize(part);
30216             });
30217             fn += type;
30218
30219             if (!this[fn]) {
30220                 this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
30221             }
30222             // Execute it right away
30223             this[fn](ref);
30224         },
30225         this);
30226     },
30227
30228     ref: function(refs) {
30229         var me = this;
30230         refs = Ext.Array.from(refs);
30231         Ext.Array.each(refs, function(info) {
30232             var ref = info.ref,
30233                 fn = 'get' + Ext.String.capitalize(ref);
30234             if (!me[fn]) {
30235                 me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
30236             }
30237         });
30238     },
30239
30240     getRef: function(ref, info, config) {
30241         this.refCache = this.refCache || {};
30242         info = info || {};
30243         config = config || {};
30244
30245         Ext.apply(info, config);
30246
30247         if (info.forceCreate) {
30248             return Ext.ComponentManager.create(info, 'component');
30249         }
30250
30251         var me = this,
30252             selector = info.selector,
30253             cached = me.refCache[ref];
30254
30255         if (!cached) {
30256             me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
30257             if (!cached && info.autoCreate) {
30258                 me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
30259             }
30260             if (cached) {
30261                 cached.on('beforedestroy', function() {
30262                     me.refCache[ref] = null;
30263                 });
30264             }
30265         }
30266
30267         return cached;
30268     },
30269
30270     /**
30271      * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an
30272      * object containing component paths mapped to a hash of listener functions.
30273      *
30274      * In the following example the `updateUser` function is mapped to to the `click`
30275      * event on a button component, which is a child of the `useredit` component.
30276      *
30277      *     Ext.define('AM.controller.Users', {
30278      *         init: function() {
30279      *             this.control({
30280      *                 'useredit button[action=save]': {
30281      *                     click: this.updateUser
30282      *                 }
30283      *             });
30284      *         },
30285      *
30286      *         updateUser: function(button) {
30287      *             console.log('clicked the Save button');
30288      *         }
30289      *     });
30290      *
30291      * See {@link Ext.ComponentQuery} for more information on component selectors.
30292      *
30293      * @param {String/Object} selectors If a String, the second argument is used as the
30294      * listeners, otherwise an object of selectors -> listeners is assumed
30295      * @param {Object} listeners
30296      */
30297     control: function(selectors, listeners) {
30298         this.application.control(selectors, listeners, this);
30299     },
30300
30301     /**
30302      * Returns instance of a {@link Ext.app.Controller controller} with the given name.
30303      * When controller doesn't exist yet, it's created.
30304      * @param {String} name
30305      * @return {Ext.app.Controller} a controller instance.
30306      */
30307     getController: function(name) {
30308         return this.application.getController(name);
30309     },
30310
30311     /**
30312      * Returns instance of a {@link Ext.data.Store Store} with the given name.
30313      * When store doesn't exist yet, it's created.
30314      * @param {String} name
30315      * @return {Ext.data.Store} a store instance.
30316      */
30317     getStore: function(name) {
30318         return this.application.getStore(name);
30319     },
30320
30321     /**
30322      * Returns a {@link Ext.data.Model Model} class with the given name.
30323      * A shorthand for using {@link Ext.ModelManager#getModel}.
30324      * @param {String} name
30325      * @return {Ext.data.Model} a model class.
30326      */
30327     getModel: function(model) {
30328         return this.application.getModel(model);
30329     },
30330
30331     /**
30332      * Returns a View class with the given name.  To create an instance of the view,
30333      * you can use it like it's used by Application to create the Viewport:
30334      * 
30335      *     this.getView('Viewport').create();
30336      * 
30337      * @param {String} name
30338      * @return {Ext.Base} a view class.
30339      */
30340     getView: function(view) {
30341         return this.application.getView(view);
30342     }
30343 });
30344
30345 /**
30346  * @author Don Griffin
30347  *
30348  * This class is a base for all id generators. It also provides lookup of id generators by
30349  * their id.
30350  * 
30351  * Generally, id generators are used to generate a primary key for new model instances. There
30352  * are different approaches to solving this problem, so this mechanism has both simple use
30353  * cases and is open to custom implementations. A {@link Ext.data.Model} requests id generation
30354  * using the {@link Ext.data.Model#idgen} property.
30355  *
30356  * # Identity, Type and Shared IdGenerators
30357  *
30358  * It is often desirable to share IdGenerators to ensure uniqueness or common configuration.
30359  * This is done by giving IdGenerator instances an id property by which they can be looked
30360  * up using the {@link #get} method. To configure two {@link Ext.data.Model Model} classes
30361  * to share one {@link Ext.data.SequentialIdGenerator sequential} id generator, you simply
30362  * assign them the same id:
30363  *
30364  *     Ext.define('MyApp.data.MyModelA', {
30365  *         extend: 'Ext.data.Model',
30366  *         idgen: {
30367  *             type: 'sequential',
30368  *             id: 'foo'
30369  *         }
30370  *     });
30371  *
30372  *     Ext.define('MyApp.data.MyModelB', {
30373  *         extend: 'Ext.data.Model',
30374  *         idgen: {
30375  *             type: 'sequential',
30376  *             id: 'foo'
30377  *         }
30378  *     });
30379  *
30380  * To make this as simple as possible for generator types that are shared by many (or all)
30381  * Models, the IdGenerator types (such as 'sequential' or 'uuid') are also reserved as
30382  * generator id's. This is used by the {@link Ext.data.UuidGenerator} which has an id equal
30383  * to its type ('uuid'). In other words, the following Models share the same generator:
30384  *
30385  *     Ext.define('MyApp.data.MyModelX', {
30386  *         extend: 'Ext.data.Model',
30387  *         idgen: 'uuid'
30388  *     });
30389  *
30390  *     Ext.define('MyApp.data.MyModelY', {
30391  *         extend: 'Ext.data.Model',
30392  *         idgen: 'uuid'
30393  *     });
30394  *
30395  * This can be overridden (by specifying the id explicitly), but there is no particularly
30396  * good reason to do so for this generator type.
30397  *
30398  * # Creating Custom Generators
30399  * 
30400  * An id generator should derive from this class and implement the {@link #generate} method.
30401  * The constructor will apply config properties on new instances, so a constructor is often
30402  * not necessary.
30403  *
30404  * To register an id generator type, a derived class should provide an `alias` like so:
30405  *
30406  *     Ext.define('MyApp.data.CustomIdGenerator', {
30407  *         extend: 'Ext.data.IdGenerator',
30408  *         alias: 'idgen.custom',
30409  *
30410  *         configProp: 42, // some config property w/default value
30411  *
30412  *         generate: function () {
30413  *             return ... // a new id
30414  *         }
30415  *     });
30416  *
30417  * Using the custom id generator is then straightforward:
30418  *
30419  *     Ext.define('MyApp.data.MyModel', {
30420  *         extend: 'Ext.data.Model',
30421  *         idgen: 'custom'
30422  *     });
30423  *     // or...
30424  *
30425  *     Ext.define('MyApp.data.MyModel', {
30426  *         extend: 'Ext.data.Model',
30427  *         idgen: {
30428  *             type: 'custom',
30429  *             configProp: value
30430  *         }
30431  *     });
30432  *
30433  * It is not recommended to mix shared generators with generator configuration. This leads
30434  * to unpredictable results unless all configurations match (which is also redundant). In
30435  * such cases, a custom generator with a default id is the best approach.
30436  *
30437  *     Ext.define('MyApp.data.CustomIdGenerator', {
30438  *         extend: 'Ext.data.SequentialIdGenerator',
30439  *         alias: 'idgen.custom',
30440  *
30441  *         id: 'custom', // shared by default
30442  *
30443  *         prefix: 'ID_',
30444  *         seed: 1000
30445  *     });
30446  *
30447  *     Ext.define('MyApp.data.MyModelX', {
30448  *         extend: 'Ext.data.Model',
30449  *         idgen: 'custom'
30450  *     });
30451  *
30452  *     Ext.define('MyApp.data.MyModelY', {
30453  *         extend: 'Ext.data.Model',
30454  *         idgen: 'custom'
30455  *     });
30456  *
30457  *     // the above models share a generator that produces ID_1000, ID_1001, etc..
30458  *
30459  */
30460 Ext.define('Ext.data.IdGenerator', {
30461
30462     isGenerator: true,
30463
30464     /**
30465      * Initializes a new instance.
30466      * @param {Object} config (optional) Configuration object to be applied to the new instance.
30467      */
30468     constructor: function(config) {
30469         var me = this;
30470
30471         Ext.apply(me, config);
30472
30473         if (me.id) {
30474             Ext.data.IdGenerator.all[me.id] = me;
30475         }
30476     },
30477
30478     /**
30479      * @cfg {String} id
30480      * The id by which to register a new instance. This instance can be found using the
30481      * {@link Ext.data.IdGenerator#get} static method.
30482      */
30483
30484     getRecId: function (rec) {
30485         return rec.modelName + '-' + rec.internalId;
30486     },
30487
30488     /**
30489      * Generates and returns the next id. This method must be implemented by the derived
30490      * class.
30491      *
30492      * @return {String} The next id.
30493      * @method generate
30494      * @abstract
30495      */
30496
30497     statics: {
30498         /**
30499          * @property {Object} all
30500          * This object is keyed by id to lookup instances.
30501          * @private
30502          * @static
30503          */
30504         all: {},
30505
30506         /**
30507          * Returns the IdGenerator given its config description.
30508          * @param {String/Object} config If this parameter is an IdGenerator instance, it is
30509          * simply returned. If this is a string, it is first used as an id for lookup and
30510          * then, if there is no match, as a type to create a new instance. This parameter
30511          * can also be a config object that contains a `type` property (among others) that
30512          * are used to create and configure the instance.
30513          * @static
30514          */
30515         get: function (config) {
30516             var generator,
30517                 id,
30518                 type;
30519
30520             if (typeof config == 'string') {
30521                 id = type = config;
30522                 config = null;
30523             } else if (config.isGenerator) {
30524                 return config;
30525             } else {
30526                 id = config.id || config.type;
30527                 type = config.type;
30528             }
30529
30530             generator = this.all[id];
30531             if (!generator) {
30532                 generator = Ext.create('idgen.' + type, config);
30533             }
30534
30535             return generator;
30536         }
30537     }
30538 });
30539
30540 /**
30541  * @class Ext.data.SortTypes
30542  * This class defines a series of static methods that are used on a
30543  * {@link Ext.data.Field} for performing sorting. The methods cast the 
30544  * underlying values into a data type that is appropriate for sorting on
30545  * that particular field.  If a {@link Ext.data.Field#type} is specified, 
30546  * the sortType will be set to a sane default if the sortType is not 
30547  * explicitly defined on the field. The sortType will make any necessary
30548  * modifications to the value and return it.
30549  * <ul>
30550  * <li><b>asText</b> - Removes any tags and converts the value to a string</li>
30551  * <li><b>asUCText</b> - Removes any tags and converts the value to an uppercase string</li>
30552  * <li><b>asUCText</b> - Converts the value to an uppercase string</li>
30553  * <li><b>asDate</b> - Converts the value into Unix epoch time</li>
30554  * <li><b>asFloat</b> - Converts the value to a floating point number</li>
30555  * <li><b>asInt</b> - Converts the value to an integer number</li>
30556  * </ul>
30557  * <p>
30558  * It is also possible to create a custom sortType that can be used throughout
30559  * an application.
30560  * <pre><code>
30561 Ext.apply(Ext.data.SortTypes, {
30562     asPerson: function(person){
30563         // expects an object with a first and last name property
30564         return person.lastName.toUpperCase() + person.firstName.toLowerCase();
30565     }    
30566 });
30567
30568 Ext.define('Employee', {
30569     extend: 'Ext.data.Model',
30570     fields: [{
30571         name: 'person',
30572         sortType: 'asPerson'
30573     }, {
30574         name: 'salary',
30575         type: 'float' // sortType set to asFloat
30576     }]
30577 });
30578  * </code></pre>
30579  * </p>
30580  * @singleton
30581  * @docauthor Evan Trimboli <evan@sencha.com>
30582  */
30583 Ext.define('Ext.data.SortTypes', {
30584     
30585     singleton: true,
30586     
30587     /**
30588      * Default sort that does nothing
30589      * @param {Object} s The value being converted
30590      * @return {Object} The comparison value
30591      */
30592     none : function(s) {
30593         return s;
30594     },
30595
30596     /**
30597      * The regular expression used to strip tags
30598      * @type {RegExp}
30599      * @property
30600      */
30601     stripTagsRE : /<\/?[^>]+>/gi,
30602
30603     /**
30604      * Strips all HTML tags to sort on text only
30605      * @param {Object} s The value being converted
30606      * @return {String} The comparison value
30607      */
30608     asText : function(s) {
30609         return String(s).replace(this.stripTagsRE, "");
30610     },
30611
30612     /**
30613      * Strips all HTML tags to sort on text only - Case insensitive
30614      * @param {Object} s The value being converted
30615      * @return {String} The comparison value
30616      */
30617     asUCText : function(s) {
30618         return String(s).toUpperCase().replace(this.stripTagsRE, "");
30619     },
30620
30621     /**
30622      * Case insensitive string
30623      * @param {Object} s The value being converted
30624      * @return {String} The comparison value
30625      */
30626     asUCString : function(s) {
30627         return String(s).toUpperCase();
30628     },
30629
30630     /**
30631      * Date sorting
30632      * @param {Object} s The value being converted
30633      * @return {Number} The comparison value
30634      */
30635     asDate : function(s) {
30636         if(!s){
30637             return 0;
30638         }
30639         if(Ext.isDate(s)){
30640             return s.getTime();
30641         }
30642         return Date.parse(String(s));
30643     },
30644
30645     /**
30646      * Float sorting
30647      * @param {Object} s The value being converted
30648      * @return {Number} The comparison value
30649      */
30650     asFloat : function(s) {
30651         var val = parseFloat(String(s).replace(/,/g, ""));
30652         return isNaN(val) ? 0 : val;
30653     },
30654
30655     /**
30656      * Integer sorting
30657      * @param {Object} s The value being converted
30658      * @return {Number} The comparison value
30659      */
30660     asInt : function(s) {
30661         var val = parseInt(String(s).replace(/,/g, ""), 10);
30662         return isNaN(val) ? 0 : val;
30663     }
30664 });
30665 /**
30666  * Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
30667  * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the
30668  * context of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching
30669  * on their records. Example usage:
30670  *
30671  *     //set up a fictional MixedCollection containing a few people to filter on
30672  *     var allNames = new Ext.util.MixedCollection();
30673  *     allNames.addAll([
30674  *         {id: 1, name: 'Ed',    age: 25},
30675  *         {id: 2, name: 'Jamie', age: 37},
30676  *         {id: 3, name: 'Abe',   age: 32},
30677  *         {id: 4, name: 'Aaron', age: 26},
30678  *         {id: 5, name: 'David', age: 32}
30679  *     ]);
30680  *
30681  *     var ageFilter = new Ext.util.Filter({
30682  *         property: 'age',
30683  *         value   : 32
30684  *     });
30685  *
30686  *     var longNameFilter = new Ext.util.Filter({
30687  *         filterFn: function(item) {
30688  *             return item.name.length > 4;
30689  *         }
30690  *     });
30691  *
30692  *     //a new MixedCollection with the 3 names longer than 4 characters
30693  *     var longNames = allNames.filter(longNameFilter);
30694  *
30695  *     //a new MixedCollection with the 2 people of age 24:
30696  *     var youngFolk = allNames.filter(ageFilter);
30697  *
30698  */
30699 Ext.define('Ext.util.Filter', {
30700
30701     /* Begin Definitions */
30702
30703     /* End Definitions */
30704     /**
30705      * @cfg {String} property
30706      * The property to filter on. Required unless a {@link #filterFn} is passed
30707      */
30708     
30709     /**
30710      * @cfg {Function} filterFn
30711      * A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} in turn. Should return
30712      * true to accept each item or false to reject it
30713      */
30714     
30715     /**
30716      * @cfg {Boolean} anyMatch
30717      * True to allow any match - no regex start/end line anchors will be added.
30718      */
30719     anyMatch: false,
30720     
30721     /**
30722      * @cfg {Boolean} exactMatch
30723      * True to force exact match (^ and $ characters added to the regex). Ignored if anyMatch is true.
30724      */
30725     exactMatch: false,
30726     
30727     /**
30728      * @cfg {Boolean} caseSensitive
30729      * True to make the regex case sensitive (adds 'i' switch to regex).
30730      */
30731     caseSensitive: false,
30732     
30733     /**
30734      * @cfg {String} root
30735      * Optional root property. This is mostly useful when filtering a Store, in which case we set the root to 'data' to
30736      * make the filter pull the {@link #property} out of the data object of each item
30737      */
30738
30739     /**
30740      * Creates new Filter.
30741      * @param {Object} [config] Config object
30742      */
30743     constructor: function(config) {
30744         var me = this;
30745         Ext.apply(me, config);
30746         
30747         //we're aliasing filter to filterFn mostly for API cleanliness reasons, despite the fact it dirties the code here.
30748         //Ext.util.Sorter takes a sorterFn property but allows .sort to be called - we do the same here
30749         me.filter = me.filter || me.filterFn;
30750         
30751         if (me.filter === undefined) {
30752             if (me.property === undefined || me.value === undefined) {
30753                 // Commented this out temporarily because it stops us using string ids in models. TODO: Remove this once
30754                 // Model has been updated to allow string ids
30755                 
30756                 // Ext.Error.raise("A Filter requires either a property or a filterFn to be set");
30757             } else {
30758                 me.filter = me.createFilterFn();
30759             }
30760             
30761             me.filterFn = me.filter;
30762         }
30763     },
30764     
30765     /**
30766      * @private
30767      * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
30768      */
30769     createFilterFn: function() {
30770         var me       = this,
30771             matcher  = me.createValueMatcher(),
30772             property = me.property;
30773         
30774         return function(item) {
30775             var value = me.getRoot.call(me, item)[property];
30776             return matcher === null ? value === null : matcher.test(value);
30777         };
30778     },
30779     
30780     /**
30781      * @private
30782      * Returns the root property of the given item, based on the configured {@link #root} property
30783      * @param {Object} item The item
30784      * @return {Object} The root property of the object
30785      */
30786     getRoot: function(item) {
30787         var root = this.root;
30788         return root === undefined ? item : item[root];
30789     },
30790     
30791     /**
30792      * @private
30793      * Returns a regular expression based on the given value and matching options
30794      */
30795     createValueMatcher : function() {
30796         var me            = this,
30797             value         = me.value,
30798             anyMatch      = me.anyMatch,
30799             exactMatch    = me.exactMatch,
30800             caseSensitive = me.caseSensitive,
30801             escapeRe      = Ext.String.escapeRegex;
30802             
30803         if (value === null) {
30804             return value;
30805         }
30806         
30807         if (!value.exec) { // not a regex
30808             value = String(value);
30809
30810             if (anyMatch === true) {
30811                 value = escapeRe(value);
30812             } else {
30813                 value = '^' + escapeRe(value);
30814                 if (exactMatch === true) {
30815                     value += '$';
30816                 }
30817             }
30818             value = new RegExp(value, caseSensitive ? '' : 'i');
30819          }
30820          
30821          return value;
30822     }
30823 });
30824 /**
30825  * Represents a single sorter that can be applied to a Store. The sorter is used
30826  * to compare two values against each other for the purpose of ordering them. Ordering
30827  * is achieved by specifying either:
30828  *
30829  * - {@link #property A sorting property}
30830  * - {@link #sorterFn A sorting function}
30831  *
30832  * As a contrived example, we can specify a custom sorter that sorts by rank:
30833  *
30834  *     Ext.define('Person', {
30835  *         extend: 'Ext.data.Model',
30836  *         fields: ['name', 'rank']
30837  *     });
30838  *
30839  *     Ext.create('Ext.data.Store', {
30840  *         model: 'Person',
30841  *         proxy: 'memory',
30842  *         sorters: [{
30843  *             sorterFn: function(o1, o2){
30844  *                 var getRank = function(o){
30845  *                     var name = o.get('rank');
30846  *                     if (name === 'first') {
30847  *                         return 1;
30848  *                     } else if (name === 'second') {
30849  *                         return 2;
30850  *                     } else {
30851  *                         return 3;
30852  *                     }
30853  *                 },
30854  *                 rank1 = getRank(o1),
30855  *                 rank2 = getRank(o2);
30856  *
30857  *                 if (rank1 === rank2) {
30858  *                     return 0;
30859  *                 }
30860  *
30861  *                 return rank1 < rank2 ? -1 : 1;
30862  *             }
30863  *         }],
30864  *         data: [{
30865  *             name: 'Person1',
30866  *             rank: 'second'
30867  *         }, {
30868  *             name: 'Person2',
30869  *             rank: 'third'
30870  *         }, {
30871  *             name: 'Person3',
30872  *             rank: 'first'
30873  *         }]
30874  *     });
30875  */
30876 Ext.define('Ext.util.Sorter', {
30877
30878     /**
30879      * @cfg {String} property
30880      * The property to sort by. Required unless {@link #sorterFn} is provided. The property is extracted from the object
30881      * directly and compared for sorting using the built in comparison operators.
30882      */
30883     
30884     /**
30885      * @cfg {Function} sorterFn
30886      * A specific sorter function to execute. Can be passed instead of {@link #property}. This sorter function allows
30887      * for any kind of custom/complex comparisons. The sorterFn receives two arguments, the objects being compared. The
30888      * function should return:
30889      *
30890      *   - -1 if o1 is "less than" o2
30891      *   - 0 if o1 is "equal" to o2
30892      *   - 1 if o1 is "greater than" o2
30893      */
30894     
30895     /**
30896      * @cfg {String} root
30897      * Optional root property. This is mostly useful when sorting a Store, in which case we set the root to 'data' to
30898      * make the filter pull the {@link #property} out of the data object of each item
30899      */
30900     
30901     /**
30902      * @cfg {Function} transform
30903      * A function that will be run on each value before it is compared in the sorter. The function will receive a single
30904      * argument, the value.
30905      */
30906     
30907     /**
30908      * @cfg {String} direction
30909      * The direction to sort by.
30910      */
30911     direction: "ASC",
30912     
30913     constructor: function(config) {
30914         var me = this;
30915         
30916         Ext.apply(me, config);
30917         
30918         
30919         me.updateSortFunction();
30920     },
30921     
30922     /**
30923      * @private
30924      * Creates and returns a function which sorts an array by the given property and direction
30925      * @return {Function} A function which sorts by the property/direction combination provided
30926      */
30927     createSortFunction: function(sorterFn) {
30928         var me        = this,
30929             property  = me.property,
30930             direction = me.direction || "ASC",
30931             modifier  = direction.toUpperCase() == "DESC" ? -1 : 1;
30932         
30933         //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
30934         //-1 if object 2 is greater or 0 if they are equal
30935         return function(o1, o2) {
30936             return modifier * sorterFn.call(me, o1, o2);
30937         };
30938     },
30939     
30940     /**
30941      * @private
30942      * Basic default sorter function that just compares the defined property of each object
30943      */
30944     defaultSorterFn: function(o1, o2) {
30945         var me = this,
30946             transform = me.transform,
30947             v1 = me.getRoot(o1)[me.property],
30948             v2 = me.getRoot(o2)[me.property];
30949             
30950         if (transform) {
30951             v1 = transform(v1);
30952             v2 = transform(v2);
30953         }
30954
30955         return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
30956     },
30957     
30958     /**
30959      * @private
30960      * Returns the root property of the given item, based on the configured {@link #root} property
30961      * @param {Object} item The item
30962      * @return {Object} The root property of the object
30963      */
30964     getRoot: function(item) {
30965         return this.root === undefined ? item : item[this.root];
30966     },
30967     
30968     /**
30969      * Set the sorting direction for this sorter.
30970      * @param {String} direction The direction to sort in. Should be either 'ASC' or 'DESC'.
30971      */
30972     setDirection: function(direction) {
30973         var me = this;
30974         me.direction = direction;
30975         me.updateSortFunction();
30976     },
30977     
30978     /**
30979      * Toggles the sorting direction for this sorter.
30980      */
30981     toggle: function() {
30982         var me = this;
30983         me.direction = Ext.String.toggle(me.direction, "ASC", "DESC");
30984         me.updateSortFunction();
30985     },
30986     
30987     /**
30988      * Update the sort function for this sorter.
30989      * @param {Function} [fn] A new sorter function for this sorter. If not specified it will use the default
30990      * sorting function.
30991      */
30992     updateSortFunction: function(fn) {
30993         var me = this;
30994         fn = fn || me.sorterFn || me.defaultSorterFn;
30995         me.sort = me.createSortFunction(fn);
30996     }
30997 });
30998 /**
30999  * @author Ed Spencer
31000  *
31001  * Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. Operation objects are
31002  * used to enable communication between Stores and Proxies. Application developers should rarely need to interact with
31003  * Operation objects directly.
31004  *
31005  * Several Operations can be batched together in a {@link Ext.data.Batch batch}.
31006  */
31007 Ext.define('Ext.data.Operation', {
31008     /**
31009      * @cfg {Boolean} synchronous
31010      * True if this Operation is to be executed synchronously. This property is inspected by a
31011      * {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not.
31012      */
31013     synchronous: true,
31014
31015     /**
31016      * @cfg {String} action
31017      * The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'.
31018      */
31019     action: undefined,
31020
31021     /**
31022      * @cfg {Ext.util.Filter[]} filters
31023      * Optional array of filter objects. Only applies to 'read' actions.
31024      */
31025     filters: undefined,
31026
31027     /**
31028      * @cfg {Ext.util.Sorter[]} sorters
31029      * Optional array of sorter objects. Only applies to 'read' actions.
31030      */
31031     sorters: undefined,
31032
31033     /**
31034      * @cfg {Ext.util.Grouper} group
31035      * Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
31036      */
31037     group: undefined,
31038
31039     /**
31040      * @cfg {Number} start
31041      * The start index (offset), used in paging when running a 'read' action.
31042      */
31043     start: undefined,
31044
31045     /**
31046      * @cfg {Number} limit
31047      * The number of records to load. Used on 'read' actions when paging is being used.
31048      */
31049     limit: undefined,
31050
31051     /**
31052      * @cfg {Ext.data.Batch} batch
31053      * The batch that this Operation is a part of.
31054      */
31055     batch: undefined,
31056
31057     /**
31058      * @cfg {Function} callback
31059      * Function to execute when operation completed.  Will be called with the following parameters:
31060      *
31061      * - records : Array of Ext.data.Model objects.
31062      * - operation : The Ext.data.Operation itself.
31063      * - success : True when operation completed successfully.
31064      */
31065     callback: undefined,
31066
31067     /**
31068      * @cfg {Object} scope
31069      * Scope for the {@link #callback} function.
31070      */
31071     scope: undefined,
31072
31073     /**
31074      * @property {Boolean} started
31075      * Read-only property tracking the start status of this Operation. Use {@link #isStarted}.
31076      * @private
31077      */
31078     started: false,
31079
31080     /**
31081      * @property {Boolean} running
31082      * Read-only property tracking the run status of this Operation. Use {@link #isRunning}.
31083      * @private
31084      */
31085     running: false,
31086
31087     /**
31088      * @property {Boolean} complete
31089      * Read-only property tracking the completion status of this Operation. Use {@link #isComplete}.
31090      * @private
31091      */
31092     complete: false,
31093
31094     /**
31095      * @property {Boolean} success
31096      * Read-only property tracking whether the Operation was successful or not. This starts as undefined and is set to true
31097      * or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
31098      * {@link #wasSuccessful} to query success status.
31099      * @private
31100      */
31101     success: undefined,
31102
31103     /**
31104      * @property {Boolean} exception
31105      * Read-only property tracking the exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
31106      * @private
31107      */
31108     exception: false,
31109
31110     /**
31111      * @property {String/Object} error
31112      * The error object passed when {@link #setException} was called. This could be any object or primitive.
31113      * @private
31114      */
31115     error: undefined,
31116
31117     /**
31118      * @property {RegExp} actionCommitRecordsRe
31119      * The RegExp used to categorize actions that require record commits.
31120      */
31121     actionCommitRecordsRe: /^(?:create|update)$/i,
31122
31123     /**
31124      * @property {RegExp} actionSkipSyncRe
31125      * The RegExp used to categorize actions that skip local record synchronization. This defaults
31126      * to match 'destroy'.
31127      */
31128     actionSkipSyncRe: /^destroy$/i,
31129
31130     /**
31131      * Creates new Operation object.
31132      * @param {Object} config (optional) Config object.
31133      */
31134     constructor: function(config) {
31135         Ext.apply(this, config || {});
31136     },
31137
31138     /**
31139      * This method is called to commit data to this instance's records given the records in
31140      * the server response. This is followed by calling {@link Ext.data.Model#commit} on all
31141      * those records (for 'create' and 'update' actions).
31142      *
31143      * If this {@link #action} is 'destroy', any server records are ignored and the
31144      * {@link Ext.data.Model#commit} method is not called.
31145      *
31146      * @param {Ext.data.Model[]} serverRecords An array of {@link Ext.data.Model} objects returned by
31147      * the server.
31148      * @markdown
31149      */
31150     commitRecords: function (serverRecords) {
31151         var me = this,
31152             mc, index, clientRecords, serverRec, clientRec;
31153
31154         if (!me.actionSkipSyncRe.test(me.action)) {
31155             clientRecords = me.records;
31156
31157             if (clientRecords && clientRecords.length) {
31158                 mc = Ext.create('Ext.util.MixedCollection', true, function(r) {return r.getId();});
31159                 mc.addAll(clientRecords);
31160
31161                 for (index = serverRecords ? serverRecords.length : 0; index--; ) {
31162                     serverRec = serverRecords[index];
31163                     clientRec = mc.get(serverRec.getId());
31164
31165                     if (clientRec) {
31166                         clientRec.beginEdit();
31167                         clientRec.set(serverRec.data);
31168                         clientRec.endEdit(true);
31169                     }
31170                 }
31171
31172                 if (me.actionCommitRecordsRe.test(me.action)) {
31173                     for (index = clientRecords.length; index--; ) {
31174                         clientRecords[index].commit();
31175                     }
31176                 }
31177             }
31178         }
31179     },
31180
31181     /**
31182      * Marks the Operation as started.
31183      */
31184     setStarted: function() {
31185         this.started = true;
31186         this.running = true;
31187     },
31188
31189     /**
31190      * Marks the Operation as completed.
31191      */
31192     setCompleted: function() {
31193         this.complete = true;
31194         this.running  = false;
31195     },
31196
31197     /**
31198      * Marks the Operation as successful.
31199      */
31200     setSuccessful: function() {
31201         this.success = true;
31202     },
31203
31204     /**
31205      * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
31206      * @param {String/Object} error (optional) error string/object
31207      */
31208     setException: function(error) {
31209         this.exception = true;
31210         this.success = false;
31211         this.running = false;
31212         this.error = error;
31213     },
31214
31215     /**
31216      * Returns true if this Operation encountered an exception (see also {@link #getError})
31217      * @return {Boolean} True if there was an exception
31218      */
31219     hasException: function() {
31220         return this.exception === true;
31221     },
31222
31223     /**
31224      * Returns the error string or object that was set using {@link #setException}
31225      * @return {String/Object} The error object
31226      */
31227     getError: function() {
31228         return this.error;
31229     },
31230
31231     /**
31232      * Returns an array of Ext.data.Model instances as set by the Proxy.
31233      * @return {Ext.data.Model[]} Any loaded Records
31234      */
31235     getRecords: function() {
31236         var resultSet = this.getResultSet();
31237
31238         return (resultSet === undefined ? this.records : resultSet.records);
31239     },
31240
31241     /**
31242      * Returns the ResultSet object (if set by the Proxy). This object will contain the {@link Ext.data.Model model}
31243      * instances as well as meta data such as number of instances fetched, number available etc
31244      * @return {Ext.data.ResultSet} The ResultSet object
31245      */
31246     getResultSet: function() {
31247         return this.resultSet;
31248     },
31249
31250     /**
31251      * Returns true if the Operation has been started. Note that the Operation may have started AND completed, see
31252      * {@link #isRunning} to test if the Operation is currently running.
31253      * @return {Boolean} True if the Operation has started
31254      */
31255     isStarted: function() {
31256         return this.started === true;
31257     },
31258
31259     /**
31260      * Returns true if the Operation has been started but has not yet completed.
31261      * @return {Boolean} True if the Operation is currently running
31262      */
31263     isRunning: function() {
31264         return this.running === true;
31265     },
31266
31267     /**
31268      * Returns true if the Operation has been completed
31269      * @return {Boolean} True if the Operation is complete
31270      */
31271     isComplete: function() {
31272         return this.complete === true;
31273     },
31274
31275     /**
31276      * Returns true if the Operation has completed and was successful
31277      * @return {Boolean} True if successful
31278      */
31279     wasSuccessful: function() {
31280         return this.isComplete() && this.success === true;
31281     },
31282
31283     /**
31284      * @private
31285      * Associates this Operation with a Batch
31286      * @param {Ext.data.Batch} batch The batch
31287      */
31288     setBatch: function(batch) {
31289         this.batch = batch;
31290     },
31291
31292     /**
31293      * Checks whether this operation should cause writing to occur.
31294      * @return {Boolean} Whether the operation should cause a write to occur.
31295      */
31296     allowWrite: function() {
31297         return this.action != 'read';
31298     }
31299 });
31300 /**
31301  * @author Ed Spencer
31302  *
31303  * This singleton contains a set of validation functions that can be used to validate any type of data. They are most
31304  * often used in {@link Ext.data.Model Models}, where they are automatically set up and executed.
31305  */
31306 Ext.define('Ext.data.validations', {
31307     singleton: true,
31308     
31309     /**
31310      * @property {String} presenceMessage
31311      * The default error message used when a presence validation fails.
31312      */
31313     presenceMessage: 'must be present',
31314     
31315     /**
31316      * @property {String} lengthMessage
31317      * The default error message used when a length validation fails.
31318      */
31319     lengthMessage: 'is the wrong length',
31320     
31321     /**
31322      * @property {Boolean} formatMessage
31323      * The default error message used when a format validation fails.
31324      */
31325     formatMessage: 'is the wrong format',
31326     
31327     /**
31328      * @property {String} inclusionMessage
31329      * The default error message used when an inclusion validation fails.
31330      */
31331     inclusionMessage: 'is not included in the list of acceptable values',
31332     
31333     /**
31334      * @property {String} exclusionMessage
31335      * The default error message used when an exclusion validation fails.
31336      */
31337     exclusionMessage: 'is not an acceptable value',
31338     
31339     /**
31340      * @property {String} emailMessage
31341      * The default error message used when an email validation fails
31342      */
31343     emailMessage: 'is not a valid email address',
31344     
31345     /**
31346      * @property {RegExp} emailRe
31347      * The regular expression used to validate email addresses
31348      */
31349     emailRe: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,
31350     
31351     /**
31352      * Validates that the given value is present.
31353      * For example:
31354      *
31355      *     validations: [{type: 'presence', field: 'age'}]
31356      *
31357      * @param {Object} config Config object
31358      * @param {Object} value The value to validate
31359      * @return {Boolean} True if validation passed
31360      */
31361     presence: function(config, value) {
31362         if (value === undefined) {
31363             value = config;
31364         }
31365         
31366         //we need an additional check for zero here because zero is an acceptable form of present data
31367         return !!value || value === 0;
31368     },
31369     
31370     /**
31371      * Returns true if the given value is between the configured min and max values.
31372      * For example:
31373      *
31374      *     validations: [{type: 'length', field: 'name', min: 2}]
31375      *
31376      * @param {Object} config Config object
31377      * @param {String} value The value to validate
31378      * @return {Boolean} True if the value passes validation
31379      */
31380     length: function(config, value) {
31381         if (value === undefined || value === null) {
31382             return false;
31383         }
31384         
31385         var length = value.length,
31386             min    = config.min,
31387             max    = config.max;
31388         
31389         if ((min && length < min) || (max && length > max)) {
31390             return false;
31391         } else {
31392             return true;
31393         }
31394     },
31395     
31396     /**
31397      * Validates that an email string is in the correct format
31398      * @param {Object} config Config object
31399      * @param {String} email The email address
31400      * @return {Boolean} True if the value passes validation
31401      */
31402     email: function(config, email) {
31403         return Ext.data.validations.emailRe.test(email);
31404     },
31405     
31406     /**
31407      * Returns true if the given value passes validation against the configured `matcher` regex.
31408      * For example:
31409      *
31410      *     validations: [{type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}]
31411      *
31412      * @param {Object} config Config object
31413      * @param {String} value The value to validate
31414      * @return {Boolean} True if the value passes the format validation
31415      */
31416     format: function(config, value) {
31417         return !!(config.matcher && config.matcher.test(value));
31418     },
31419     
31420     /**
31421      * Validates that the given value is present in the configured `list`.
31422      * For example:
31423      *
31424      *     validations: [{type: 'inclusion', field: 'gender', list: ['Male', 'Female']}]
31425      *
31426      * @param {Object} config Config object
31427      * @param {String} value The value to validate
31428      * @return {Boolean} True if the value is present in the list
31429      */
31430     inclusion: function(config, value) {
31431         return config.list && Ext.Array.indexOf(config.list,value) != -1;
31432     },
31433     
31434     /**
31435      * Validates that the given value is present in the configured `list`.
31436      * For example:
31437      *
31438      *     validations: [{type: 'exclusion', field: 'username', list: ['Admin', 'Operator']}]
31439      *
31440      * @param {Object} config Config object
31441      * @param {String} value The value to validate
31442      * @return {Boolean} True if the value is not present in the list
31443      */
31444     exclusion: function(config, value) {
31445         return config.list && Ext.Array.indexOf(config.list,value) == -1;
31446     }
31447 });
31448 /**
31449  * @author Ed Spencer
31450  *
31451  * Simple wrapper class that represents a set of records returned by a Proxy.
31452  */
31453 Ext.define('Ext.data.ResultSet', {
31454     /**
31455      * @cfg {Boolean} loaded
31456      * True if the records have already been loaded. This is only meaningful when dealing with
31457      * SQL-backed proxies.
31458      */
31459     loaded: true,
31460
31461     /**
31462      * @cfg {Number} count
31463      * The number of records in this ResultSet. Note that total may differ from this number.
31464      */
31465     count: 0,
31466
31467     /**
31468      * @cfg {Number} total
31469      * The total number of records reported by the data source. This ResultSet may form a subset of
31470      * those records (see {@link #count}).
31471      */
31472     total: 0,
31473
31474     /**
31475      * @cfg {Boolean} success
31476      * True if the ResultSet loaded successfully, false if any errors were encountered.
31477      */
31478     success: false,
31479
31480     /**
31481      * @cfg {Ext.data.Model[]} records (required)
31482      * The array of record instances.
31483      */
31484
31485     /**
31486      * Creates the resultSet
31487      * @param {Object} [config] Config object.
31488      */
31489     constructor: function(config) {
31490         Ext.apply(this, config);
31491
31492         /**
31493          * @property {Number} totalRecords
31494          * Copy of this.total.
31495          * @deprecated Will be removed in Ext JS 5.0. Use {@link #total} instead.
31496          */
31497         this.totalRecords = this.total;
31498
31499         if (config.count === undefined) {
31500             this.count = this.records.length;
31501         }
31502     }
31503 });
31504 /**
31505  * @author Ed Spencer
31506  *
31507  * Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class is responsible for taking a
31508  * set of {@link Ext.data.Operation} objects and a {@link Ext.data.Request} object and modifying that request based on
31509  * the Operations.
31510  *
31511  * For example a Ext.data.writer.Json would format the Operations and their {@link Ext.data.Model} instances based on
31512  * the config options passed to the JsonWriter's constructor.
31513  *
31514  * Writers are not needed for any kind of local storage - whether via a {@link Ext.data.proxy.WebStorage Web Storage
31515  * proxy} (see {@link Ext.data.proxy.LocalStorage localStorage} and {@link Ext.data.proxy.SessionStorage
31516  * sessionStorage}) or just in memory via a {@link Ext.data.proxy.Memory MemoryProxy}.
31517  */
31518 Ext.define('Ext.data.writer.Writer', {
31519     alias: 'writer.base',
31520     alternateClassName: ['Ext.data.DataWriter', 'Ext.data.Writer'],
31521     
31522     /**
31523      * @cfg {Boolean} writeAllFields
31524      * True to write all fields from the record to the server. If set to false it will only send the fields that were
31525      * modified. Note that any fields that have {@link Ext.data.Field#persist} set to false will still be ignored.
31526      */
31527     writeAllFields: true,
31528     
31529     /**
31530      * @cfg {String} nameProperty
31531      * This property is used to read the key for each value that will be sent to the server. For example:
31532      *
31533      *     Ext.define('Person', {
31534      *         extend: 'Ext.data.Model',
31535      *         fields: [{
31536      *             name: 'first',
31537      *             mapping: 'firstName'
31538      *         }, {
31539      *             name: 'last',
31540      *             mapping: 'lastName'
31541      *         }, {
31542      *             name: 'age'
31543      *         }]
31544      *     });
31545      *     new Ext.data.writer.Writer({
31546      *         writeAllFields: true,
31547      *         nameProperty: 'mapping'
31548      *     });
31549      *
31550      *     // This will be sent to the server
31551      *     {
31552      *         firstName: 'first name value',
31553      *         lastName: 'last name value',
31554      *         age: 1
31555      *     }
31556      *
31557      * If the value is not present, the field name will always be used.
31558      */
31559     nameProperty: 'name',
31560
31561     /**
31562      * Creates new Writer.
31563      * @param {Object} [config] Config object.
31564      */
31565     constructor: function(config) {
31566         Ext.apply(this, config);
31567     },
31568
31569     /**
31570      * Prepares a Proxy's Ext.data.Request object
31571      * @param {Ext.data.Request} request The request object
31572      * @return {Ext.data.Request} The modified request object
31573      */
31574     write: function(request) {
31575         var operation = request.operation,
31576             records   = operation.records || [],
31577             len       = records.length,
31578             i         = 0,
31579             data      = [];
31580
31581         for (; i < len; i++) {
31582             data.push(this.getRecordData(records[i]));
31583         }
31584         return this.writeRecords(request, data);
31585     },
31586
31587     /**
31588      * Formats the data for each record before sending it to the server. This method should be overridden to format the
31589      * data in a way that differs from the default.
31590      * @param {Object} record The record that we are writing to the server.
31591      * @return {Object} An object literal of name/value keys to be written to the server. By default this method returns
31592      * the data property on the record.
31593      */
31594     getRecordData: function(record) {
31595         var isPhantom = record.phantom === true,
31596             writeAll = this.writeAllFields || isPhantom,
31597             nameProperty = this.nameProperty,
31598             fields = record.fields,
31599             data = {},
31600             changes,
31601             name,
31602             field,
31603             key;
31604         
31605         if (writeAll) {
31606             fields.each(function(field){
31607                 if (field.persist) {
31608                     name = field[nameProperty] || field.name;
31609                     data[name] = record.get(field.name);
31610                 }
31611             });
31612         } else {
31613             // Only write the changes
31614             changes = record.getChanges();
31615             for (key in changes) {
31616                 if (changes.hasOwnProperty(key)) {
31617                     field = fields.get(key);
31618                     name = field[nameProperty] || field.name;
31619                     data[name] = changes[key];
31620                 }
31621             }
31622             if (!isPhantom) {
31623                 // always include the id for non phantoms
31624                 data[record.idProperty] = record.getId();
31625             }
31626         }
31627         return data;
31628     }
31629 });
31630
31631 /**
31632  * A mixin to add floating capability to a Component.
31633  */
31634 Ext.define('Ext.util.Floating', {
31635
31636     uses: ['Ext.Layer', 'Ext.window.Window'],
31637
31638     /**
31639      * @cfg {Boolean} focusOnToFront
31640      * Specifies whether the floated component should be automatically {@link Ext.Component#focus focused} when
31641      * it is {@link #toFront brought to the front}.
31642      */
31643     focusOnToFront: true,
31644
31645     /**
31646      * @cfg {String/Boolean} shadow
31647      * Specifies whether the floating component should be given a shadow. Set to true to automatically create an {@link
31648      * Ext.Shadow}, or a string indicating the shadow's display {@link Ext.Shadow#mode}. Set to false to disable the
31649      * shadow.
31650      */
31651     shadow: 'sides',
31652
31653     constructor: function(config) {
31654         var me = this;
31655         
31656         me.floating = true;
31657         me.el = Ext.create('Ext.Layer', Ext.apply({}, config, {
31658             hideMode: me.hideMode,
31659             hidden: me.hidden,
31660             shadow: Ext.isDefined(me.shadow) ? me.shadow : 'sides',
31661             shadowOffset: me.shadowOffset,
31662             constrain: false,
31663             shim: me.shim === false ? false : undefined
31664         }), me.el);
31665     },
31666
31667     onFloatRender: function() {
31668         var me = this;
31669         me.zIndexParent = me.getZIndexParent();
31670         me.setFloatParent(me.ownerCt);
31671         delete me.ownerCt;
31672
31673         if (me.zIndexParent) {
31674             me.zIndexParent.registerFloatingItem(me);
31675         } else {
31676             Ext.WindowManager.register(me);
31677         }
31678     },
31679
31680     setFloatParent: function(floatParent) {
31681         var me = this;
31682
31683         // Remove listeners from previous floatParent
31684         if (me.floatParent) {
31685             me.mun(me.floatParent, {
31686                 hide: me.onFloatParentHide,
31687                 show: me.onFloatParentShow,
31688                 scope: me
31689             });
31690         }
31691
31692         me.floatParent = floatParent;
31693
31694         // Floating Components as children of Containers must hide when their parent hides.
31695         if (floatParent) {
31696             me.mon(me.floatParent, {
31697                 hide: me.onFloatParentHide,
31698                 show: me.onFloatParentShow,
31699                 scope: me
31700             });
31701         }
31702
31703         // If a floating Component is configured to be constrained, but has no configured
31704         // constrainTo setting, set its constrainTo to be it's ownerCt before rendering.
31705         if ((me.constrain || me.constrainHeader) && !me.constrainTo) {
31706             me.constrainTo = floatParent ? floatParent.getTargetEl() : me.container;
31707         }
31708     },
31709
31710     onFloatParentHide: function() {
31711         var me = this;
31712         
31713         if (me.hideOnParentHide !== false) {
31714             me.showOnParentShow = me.isVisible();
31715             me.hide();
31716         }
31717     },
31718
31719     onFloatParentShow: function() {
31720         if (this.showOnParentShow) {
31721             delete this.showOnParentShow;
31722             this.show();
31723         }
31724     },
31725
31726     /**
31727      * @private
31728      * Finds the ancestor Container responsible for allocating zIndexes for the passed Component.
31729      *
31730      * That will be the outermost floating Container (a Container which has no ownerCt and has floating:true).
31731      *
31732      * If we have no ancestors, or we walk all the way up to the document body, there's no zIndexParent,
31733      * and the global Ext.WindowManager will be used.
31734      */
31735     getZIndexParent: function() {
31736         var p = this.ownerCt,
31737             c;
31738
31739         if (p) {
31740             while (p) {
31741                 c = p;
31742                 p = p.ownerCt;
31743             }
31744             if (c.floating) {
31745                 return c;
31746             }
31747         }
31748     },
31749
31750     // private
31751     // z-index is managed by the zIndexManager and may be overwritten at any time.
31752     // Returns the next z-index to be used.
31753     // If this is a Container, then it will have rebased any managed floating Components,
31754     // and so the next available z-index will be approximately 10000 above that.
31755     setZIndex: function(index) {
31756         var me = this;
31757         me.el.setZIndex(index);
31758
31759         // Next item goes 10 above;
31760         index += 10;
31761
31762         // When a Container with floating items has its z-index set, it rebases any floating items it is managing.
31763         // The returned value is a round number approximately 10000 above the last z-index used.
31764         if (me.floatingItems) {
31765             index = Math.floor(me.floatingItems.setBase(index) / 100) * 100 + 10000;
31766         }
31767         return index;
31768     },
31769
31770     /**
31771      * Moves this floating Component into a constrain region.
31772      *
31773      * By default, this Component is constrained to be within the container it was added to, or the element it was
31774      * rendered to.
31775      *
31776      * An alternative constraint may be passed.
31777      * @param {String/HTMLElement/Ext.Element/Ext.util.Region} constrainTo (Optional) The Element or {@link Ext.util.Region Region} into which this Component is
31778      * to be constrained. Defaults to the element into which this floating Component was rendered.
31779      */
31780     doConstrain: function(constrainTo) {
31781         var me = this,
31782             vector = me.getConstrainVector(constrainTo || me.el.getScopeParent()),
31783             xy;
31784
31785         if (vector) {
31786             xy = me.getPosition();
31787             xy[0] += vector[0];
31788             xy[1] += vector[1];
31789             me.setPosition(xy);
31790         }
31791     },
31792
31793
31794     /**
31795      * Gets the x/y offsets to constrain this float
31796      * @private
31797      * @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.
31798      * @return {Number[]} The x/y constraints
31799      */
31800     getConstrainVector: function(constrainTo){
31801         var me = this,
31802             el;
31803
31804         if (me.constrain || me.constrainHeader) {
31805             el = me.constrainHeader ? me.header.el : me.el;
31806             constrainTo = constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container;
31807             return el.getConstrainVector(constrainTo);
31808         }
31809     },
31810
31811     /**
31812      * Aligns this floating Component to the specified element
31813      *
31814      * @param {Ext.Component/Ext.Element/HTMLElement/String} element
31815      * The element or {@link Ext.Component} to align to. If passing a component, it must be a
31816      * omponent instance. If a string id is passed, it will be used as an element id.
31817      * @param {String} [position="tl-bl?"] The position to align to (see {@link
31818      * Ext.Element#alignTo} for more details).
31819      * @param {Number[]} [offsets] Offset the positioning by [x, y]
31820      * @return {Ext.Component} this
31821      */
31822     alignTo: function(element, position, offsets) {
31823         if (element.isComponent) {
31824             element = element.getEl();
31825         }
31826         var xy = this.el.getAlignToXY(element, position, offsets);
31827         this.setPagePosition(xy);
31828         return this;
31829     },
31830
31831     /**
31832      * Brings this floating Component to the front of any other visible, floating Components managed by the same {@link
31833      * Ext.ZIndexManager ZIndexManager}
31834      *
31835      * If this Component is modal, inserts the modal mask just below this Component in the z-index stack.
31836      *
31837      * @param {Boolean} [preventFocus=false] Specify `true` to prevent the Component from being focused.
31838      * @return {Ext.Component} this
31839      */
31840     toFront: function(preventFocus) {
31841         var me = this;
31842
31843         // Find the floating Component which provides the base for this Component's zIndexing.
31844         // That must move to front to then be able to rebase its zIndex stack and move this to the front
31845         if (me.zIndexParent) {
31846             me.zIndexParent.toFront(true);
31847         }
31848         if (me.zIndexManager.bringToFront(me)) {
31849             if (!Ext.isDefined(preventFocus)) {
31850                 preventFocus = !me.focusOnToFront;
31851             }
31852             if (!preventFocus) {
31853                 // Kick off a delayed focus request.
31854                 // If another floating Component is toFronted before the delay expires
31855                 // this will not receive focus.
31856                 me.focus(false, true);
31857             }
31858         }
31859         return me;
31860     },
31861
31862     /**
31863      * This method is called internally by {@link Ext.ZIndexManager} to signal that a floating Component has either been
31864      * moved to the top of its zIndex stack, or pushed from the top of its zIndex stack.
31865      *
31866      * If a _Window_ is superceded by another Window, deactivating it hides its shadow.
31867      *
31868      * This method also fires the {@link Ext.Component#activate activate} or
31869      * {@link Ext.Component#deactivate deactivate} event depending on which action occurred.
31870      *
31871      * @param {Boolean} [active=false] True to activate the Component, false to deactivate it.
31872      * @param {Ext.Component} [newActive] The newly active Component which is taking over topmost zIndex position.
31873      */
31874     setActive: function(active, newActive) {
31875         var me = this;
31876         
31877         if (active) {
31878             if (me.el.shadow && !me.maximized) {
31879                 me.el.enableShadow(true);
31880             }
31881             me.fireEvent('activate', me);
31882         } else {
31883             // Only the *Windows* in a zIndex stack share a shadow. All other types of floaters
31884             // can keep their shadows all the time
31885             if ((me instanceof Ext.window.Window) && (newActive instanceof Ext.window.Window)) {
31886                 me.el.disableShadow();
31887             }
31888             me.fireEvent('deactivate', me);
31889         }
31890     },
31891
31892     /**
31893      * Sends this Component to the back of (lower z-index than) any other visible windows
31894      * @return {Ext.Component} this
31895      */
31896     toBack: function() {
31897         this.zIndexManager.sendToBack(this);
31898         return this;
31899     },
31900
31901     /**
31902      * Center this Component in its container.
31903      * @return {Ext.Component} this
31904      */
31905     center: function() {
31906         var me = this,
31907             xy = me.el.getAlignToXY(me.container, 'c-c');
31908         me.setPagePosition(xy);
31909         return me;
31910     },
31911
31912     // private
31913     syncShadow : function(){
31914         if (this.floating) {
31915             this.el.sync(true);
31916         }
31917     },
31918
31919     // private
31920     fitContainer: function() {
31921         var parent = this.floatParent,
31922             container = parent ? parent.getTargetEl() : this.container,
31923             size = container.getViewSize(false);
31924
31925         this.setSize(size);
31926     }
31927 });
31928 /**
31929  * Base Layout class - extended by ComponentLayout and ContainerLayout
31930  */
31931 Ext.define('Ext.layout.Layout', {
31932
31933     /* Begin Definitions */
31934
31935     /* End Definitions */
31936
31937     isLayout: true,
31938     initialized: false,
31939
31940     statics: {
31941         create: function(layout, defaultType) {
31942             var type;
31943             if (layout instanceof Ext.layout.Layout) {
31944                 return Ext.createByAlias('layout.' + layout);
31945             } else {
31946                 if (!layout || typeof layout === 'string') {
31947                     type = layout || defaultType;
31948                     layout = {};                    
31949                 }
31950                 else {
31951                     type = layout.type || defaultType;
31952                 }
31953                 return Ext.createByAlias('layout.' + type, layout || {});
31954             }
31955         }
31956     },
31957
31958     constructor : function(config) {
31959         this.id = Ext.id(null, this.type + '-');
31960         Ext.apply(this, config);
31961     },
31962
31963     /**
31964      * @private
31965      */
31966     layout : function() {
31967         var me = this;
31968         me.layoutBusy = true;
31969         me.initLayout();
31970
31971         if (me.beforeLayout.apply(me, arguments) !== false) {
31972             me.layoutCancelled = false;
31973             me.onLayout.apply(me, arguments);
31974             me.childrenChanged = false;
31975             me.owner.needsLayout = false;
31976             me.layoutBusy = false;
31977             me.afterLayout.apply(me, arguments);
31978         }
31979         else {
31980             me.layoutCancelled = true;
31981         }
31982         me.layoutBusy = false;
31983         me.doOwnerCtLayouts();
31984     },
31985
31986     beforeLayout : function() {
31987         this.renderChildren();
31988         return true;
31989     },
31990
31991     renderChildren: function () {
31992         this.renderItems(this.getLayoutItems(), this.getRenderTarget());
31993     },
31994
31995     /**
31996      * @private
31997      * Iterates over all passed items, ensuring they are rendered.  If the items are already rendered,
31998      * also determines if the items are in the proper place dom.
31999      */
32000     renderItems : function(items, target) {
32001         var me = this,
32002             ln = items.length,
32003             i = 0,
32004             item;
32005
32006         for (; i < ln; i++) {
32007             item = items[i];
32008             if (item && !item.rendered) {
32009                 me.renderItem(item, target, i);
32010             } else if (!me.isValidParent(item, target, i)) {
32011                 me.moveItem(item, target, i);
32012             } else {
32013                 // still need to configure the item, it may have moved in the container.
32014                 me.configureItem(item);
32015             }
32016         }
32017     },
32018
32019     // @private - Validates item is in the proper place in the dom.
32020     isValidParent : function(item, target, position) {
32021         var dom = item.el ? item.el.dom : Ext.getDom(item);
32022         if (dom && target && target.dom) {
32023             if (Ext.isNumber(position) && dom !== target.dom.childNodes[position]) {
32024                 return false;
32025             }
32026             return (dom.parentNode == (target.dom || target));
32027         }
32028         return false;
32029     },
32030
32031     /**
32032      * @private
32033      * Renders the given Component into the target Element.
32034      * @param {Ext.Component} item The Component to render
32035      * @param {Ext.Element} target The target Element
32036      * @param {Number} position The position within the target to render the item to
32037      */
32038     renderItem : function(item, target, position) {
32039         var me = this;
32040         if (!item.rendered) {
32041             if (me.itemCls) {
32042                 item.addCls(me.itemCls);
32043             }
32044             if (me.owner.itemCls) {
32045                 item.addCls(me.owner.itemCls);
32046             }
32047             item.render(target, position);
32048             me.configureItem(item);
32049             me.childrenChanged = true;
32050         }
32051     },
32052
32053     /**
32054      * @private
32055      * Moved Component to the provided target instead.
32056      */
32057     moveItem : function(item, target, position) {
32058         // Make sure target is a dom element
32059         target = target.dom || target;
32060         if (typeof position == 'number') {
32061             position = target.childNodes[position];
32062         }
32063         target.insertBefore(item.el.dom, position || null);
32064         item.container = Ext.get(target);
32065         this.configureItem(item);
32066         this.childrenChanged = true;
32067     },
32068
32069     /**
32070      * @private
32071      * Adds the layout's targetCls if necessary and sets
32072      * initialized flag when complete.
32073      */
32074     initLayout : function() {
32075         var me = this,
32076             targetCls = me.targetCls;
32077             
32078         if (!me.initialized && !Ext.isEmpty(targetCls)) {
32079             me.getTarget().addCls(targetCls);
32080         }
32081         me.initialized = true;
32082     },
32083
32084     // @private Sets the layout owner
32085     setOwner : function(owner) {
32086         this.owner = owner;
32087     },
32088
32089     // @private - Returns empty array
32090     getLayoutItems : function() {
32091         return [];
32092     },
32093
32094     /**
32095      * @private
32096      * Applies itemCls
32097      * Empty template method
32098      */
32099     configureItem: Ext.emptyFn,
32100     
32101     // Placeholder empty functions for subclasses to extend
32102     onLayout : Ext.emptyFn,
32103     afterLayout : Ext.emptyFn,
32104     onRemove : Ext.emptyFn,
32105     onDestroy : Ext.emptyFn,
32106     doOwnerCtLayouts : Ext.emptyFn,
32107
32108     /**
32109      * @private
32110      * Removes itemCls
32111      */
32112     afterRemove : function(item) {
32113         var el = item.el,
32114             owner = this.owner,
32115             itemCls = this.itemCls,
32116             ownerCls = owner.itemCls;
32117             
32118         // Clear managed dimensions flag when removed from the layout.
32119         if (item.rendered && !item.isDestroyed) {
32120             if (itemCls) {
32121                 el.removeCls(itemCls);
32122             }
32123             if (ownerCls) {
32124                 el.removeCls(ownerCls);
32125             }
32126         }
32127
32128         // These flags are set at the time a child item is added to a layout.
32129         // The layout must decide if it is managing the item's width, or its height, or both.
32130         // See AbstractComponent for docs on these properties.
32131         delete item.layoutManagedWidth;
32132         delete item.layoutManagedHeight;
32133     },
32134
32135     /**
32136      * Destroys this layout. This is a template method that is empty by default, but should be implemented
32137      * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes.
32138      * @template
32139      */
32140     destroy : function() {
32141         var targetCls = this.targetCls,
32142             target;
32143         
32144         if (!Ext.isEmpty(targetCls)) {
32145             target = this.getTarget();
32146             if (target) {
32147                 target.removeCls(targetCls);
32148             }
32149         }
32150         this.onDestroy();
32151     }
32152 });
32153 /**
32154  * @class Ext.ZIndexManager
32155  * <p>A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
32156  * and Component activation behavior, including masking below the active (topmost) Component.</p>
32157  * <p>{@link Ext.Component#floating Floating} Components which are rendered directly into the document (such as {@link Ext.window.Window Window}s) which are
32158  * {@link Ext.Component#show show}n are managed by a {@link Ext.WindowManager global instance}.</p>
32159  * <p>{@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating} <i>Containers</i>
32160  * (for example a {@link Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window}, or a {@link Ext.menu.Menu Menu}),
32161  * are managed by a ZIndexManager owned by that floating Container. Therefore ComboBox dropdowns within Windows will have managed z-indices
32162  * guaranteed to be correct, relative to the Window.</p>
32163  */
32164 Ext.define('Ext.ZIndexManager', {
32165
32166     alternateClassName: 'Ext.WindowGroup',
32167
32168     statics: {
32169         zBase : 9000
32170     },
32171
32172     constructor: function(container) {
32173         var me = this;
32174
32175         me.list = {};
32176         me.zIndexStack = [];
32177         me.front = null;
32178
32179         if (container) {
32180
32181             // This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
32182             if (container.isContainer) {
32183                 container.on('resize', me._onContainerResize, me);
32184                 me.zseed = Ext.Number.from(container.getEl().getStyle('zIndex'), me.getNextZSeed());
32185                 // The containing element we will be dealing with (eg masking) is the content target
32186                 me.targetEl = container.getTargetEl();
32187                 me.container = container;
32188             }
32189             // This is the ZIndexManager for a DOM element
32190             else {
32191                 Ext.EventManager.onWindowResize(me._onContainerResize, me);
32192                 me.zseed = me.getNextZSeed();
32193                 me.targetEl = Ext.get(container);
32194             }
32195         }
32196         // No container passed means we are the global WindowManager. Our target is the doc body.
32197         // DOM must be ready to collect that ref.
32198         else {
32199             Ext.EventManager.onWindowResize(me._onContainerResize, me);
32200             me.zseed = me.getNextZSeed();
32201             Ext.onDocumentReady(function() {
32202                 me.targetEl = Ext.getBody();
32203             });
32204         }
32205     },
32206
32207     getNextZSeed: function() {
32208         return (Ext.ZIndexManager.zBase += 10000);
32209     },
32210
32211     setBase: function(baseZIndex) {
32212         this.zseed = baseZIndex;
32213         return this.assignZIndices();
32214     },
32215
32216     // private
32217     assignZIndices: function() {
32218         var a = this.zIndexStack,
32219             len = a.length,
32220             i = 0,
32221             zIndex = this.zseed,
32222             comp;
32223
32224         for (; i < len; i++) {
32225             comp = a[i];
32226             if (comp && !comp.hidden) {
32227
32228                 // Setting the zIndex of a Component returns the topmost zIndex consumed by
32229                 // that Component.
32230                 // If it's just a plain floating Component such as a BoundList, then the
32231                 // return value is the passed value plus 10, ready for the next item.
32232                 // If a floating *Container* has its zIndex set, it re-orders its managed
32233                 // floating children, starting from that new base, and returns a value 10000 above
32234                 // the highest zIndex which it allocates.
32235                 zIndex = comp.setZIndex(zIndex);
32236             }
32237         }
32238         this._activateLast();
32239         return zIndex;
32240     },
32241
32242     // private
32243     _setActiveChild: function(comp) {
32244         if (comp !== this.front) {
32245
32246             if (this.front) {
32247                 this.front.setActive(false, comp);
32248             }
32249             this.front = comp;
32250             if (comp) {
32251                 comp.setActive(true);
32252                 if (comp.modal) {
32253                     this._showModalMask(comp);
32254                 }
32255             }
32256         }
32257     },
32258
32259     // private
32260     _activateLast: function(justHidden) {
32261         var comp,
32262             lastActivated = false,
32263             i;
32264
32265         // Go down through the z-index stack.
32266         // Activate the next visible one down.
32267         // Keep going down to find the next visible modal one to shift the modal mask down under
32268         for (i = this.zIndexStack.length-1; i >= 0; --i) {
32269             comp = this.zIndexStack[i];
32270             if (!comp.hidden) {
32271                 if (!lastActivated) {
32272                     this._setActiveChild(comp);
32273                     lastActivated = true;
32274                 }
32275
32276                 // Move any modal mask down to just under the next modal floater down the stack
32277                 if (comp.modal) {
32278                     this._showModalMask(comp);
32279                     return;
32280                 }
32281             }
32282         }
32283
32284         // none to activate, so there must be no modal mask.
32285         // And clear the currently active property
32286         this._hideModalMask();
32287         if (!lastActivated) {
32288             this._setActiveChild(null);
32289         }
32290     },
32291
32292     _showModalMask: function(comp) {
32293         var zIndex = comp.el.getStyle('zIndex') - 4,
32294             maskTarget = comp.floatParent ? comp.floatParent.getTargetEl() : Ext.get(comp.getEl().dom.parentNode),
32295             parentBox;
32296         
32297         if (!maskTarget) {
32298             return;
32299         }
32300         
32301         parentBox = maskTarget.getBox();
32302
32303         if (!this.mask) {
32304             this.mask = Ext.getBody().createChild({
32305                 cls: Ext.baseCSSPrefix + 'mask'
32306             });
32307             this.mask.setVisibilityMode(Ext.Element.DISPLAY);
32308             this.mask.on('click', this._onMaskClick, this);
32309         }
32310         if (maskTarget.dom === document.body) {
32311             parentBox.height = Ext.Element.getViewHeight();
32312         }
32313         maskTarget.addCls(Ext.baseCSSPrefix + 'body-masked');
32314         this.mask.setBox(parentBox);
32315         this.mask.setStyle('zIndex', zIndex);
32316         this.mask.show();
32317     },
32318
32319     _hideModalMask: function() {
32320         if (this.mask && this.mask.dom.parentNode) {
32321             Ext.get(this.mask.dom.parentNode).removeCls(Ext.baseCSSPrefix + 'body-masked');
32322             this.mask.hide();
32323         }
32324     },
32325
32326     _onMaskClick: function() {
32327         if (this.front) {
32328             this.front.focus();
32329         }
32330     },
32331
32332     _onContainerResize: function() {
32333         if (this.mask && this.mask.isVisible()) {
32334             this.mask.setSize(Ext.get(this.mask.dom.parentNode).getViewSize(true));
32335         }
32336     },
32337
32338     /**
32339      * <p>Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
32340      * need to be called under normal circumstances. Floating Components (such as Windows, BoundLists and Menus) are automatically registered
32341      * with a {@link Ext.Component#zIndexManager zIndexManager} at render time.</p>
32342      * <p>Where this may be useful is moving Windows between two ZIndexManagers. For example,
32343      * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
32344      * ZIndexManager in the desktop sample app:</p><code><pre>
32345 MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
32346 </pre></code>
32347      * @param {Ext.Component} comp The Component to register.
32348      */
32349     register : function(comp) {
32350         if (comp.zIndexManager) {
32351             comp.zIndexManager.unregister(comp);
32352         }
32353         comp.zIndexManager = this;
32354
32355         this.list[comp.id] = comp;
32356         this.zIndexStack.push(comp);
32357         comp.on('hide', this._activateLast, this);
32358     },
32359
32360     /**
32361      * <p>Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
32362      * need to be called. Components are automatically unregistered upon destruction.
32363      * See {@link #register}.</p>
32364      * @param {Ext.Component} comp The Component to unregister.
32365      */
32366     unregister : function(comp) {
32367         delete comp.zIndexManager;
32368         if (this.list && this.list[comp.id]) {
32369             delete this.list[comp.id];
32370             comp.un('hide', this._activateLast);
32371             Ext.Array.remove(this.zIndexStack, comp);
32372
32373             // Destruction requires that the topmost visible floater be activated. Same as hiding.
32374             this._activateLast(comp);
32375         }
32376     },
32377
32378     /**
32379      * Gets a registered Component by id.
32380      * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
32381      * @return {Ext.Component}
32382      */
32383     get : function(id) {
32384         return typeof id == "object" ? id : this.list[id];
32385     },
32386
32387    /**
32388      * Brings the specified Component to the front of any other active Components in this ZIndexManager.
32389      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
32390      * @return {Boolean} True if the dialog was brought to the front, else false
32391      * if it was already in front
32392      */
32393     bringToFront : function(comp) {
32394         comp = this.get(comp);
32395         if (comp !== this.front) {
32396             Ext.Array.remove(this.zIndexStack, comp);
32397             this.zIndexStack.push(comp);
32398             this.assignZIndices();
32399             return true;
32400         }
32401         if (comp.modal) {
32402             this._showModalMask(comp);
32403         }
32404         return false;
32405     },
32406
32407     /**
32408      * Sends the specified Component to the back of other active Components in this ZIndexManager.
32409      * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
32410      * @return {Ext.Component} The Component
32411      */
32412     sendToBack : function(comp) {
32413         comp = this.get(comp);
32414         Ext.Array.remove(this.zIndexStack, comp);
32415         this.zIndexStack.unshift(comp);
32416         this.assignZIndices();
32417         return comp;
32418     },
32419
32420     /**
32421      * Hides all Components managed by this ZIndexManager.
32422      */
32423     hideAll : function() {
32424         for (var id in this.list) {
32425             if (this.list[id].isComponent && this.list[id].isVisible()) {
32426                 this.list[id].hide();
32427             }
32428         }
32429     },
32430
32431     /**
32432      * @private
32433      * Temporarily hides all currently visible managed Components. This is for when
32434      * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
32435      * they should all be hidden just for the duration of the drag.
32436      */
32437     hide: function() {
32438         var i = 0,
32439             ln = this.zIndexStack.length,
32440             comp;
32441
32442         this.tempHidden = [];
32443         for (; i < ln; i++) {
32444             comp = this.zIndexStack[i];
32445             if (comp.isVisible()) {
32446                 this.tempHidden.push(comp);
32447                 comp.hide();
32448             }
32449         }
32450     },
32451
32452     /**
32453      * @private
32454      * Restores temporarily hidden managed Components to visibility.
32455      */
32456     show: function() {
32457         var i = 0,
32458             ln = this.tempHidden.length,
32459             comp,
32460             x,
32461             y;
32462
32463         for (; i < ln; i++) {
32464             comp = this.tempHidden[i];
32465             x = comp.x;
32466             y = comp.y;
32467             comp.show();
32468             comp.setPosition(x, y);
32469         }
32470         delete this.tempHidden;
32471     },
32472
32473     /**
32474      * Gets the currently-active Component in this ZIndexManager.
32475      * @return {Ext.Component} The active Component
32476      */
32477     getActive : function() {
32478         return this.front;
32479     },
32480
32481     /**
32482      * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
32483      * The function should accept a single {@link Ext.Component} reference as its only argument and should
32484      * return true if the Component matches the search criteria, otherwise it should return false.
32485      * @param {Function} fn The search function
32486      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component being tested.
32487      * that gets passed to the function if not specified)
32488      * @return {Array} An array of zero or more matching windows
32489      */
32490     getBy : function(fn, scope) {
32491         var r = [],
32492             i = 0,
32493             len = this.zIndexStack.length,
32494             comp;
32495
32496         for (; i < len; i++) {
32497             comp = this.zIndexStack[i];
32498             if (fn.call(scope||comp, comp) !== false) {
32499                 r.push(comp);
32500             }
32501         }
32502         return r;
32503     },
32504
32505     /**
32506      * Executes the specified function once for every Component in this ZIndexManager, passing each
32507      * Component as the only parameter. Returning false from the function will stop the iteration.
32508      * @param {Function} fn The function to execute for each item
32509      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Component in the iteration.
32510      */
32511     each : function(fn, scope) {
32512         var comp;
32513         for (var id in this.list) {
32514             comp = this.list[id];
32515             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32516                 return;
32517             }
32518         }
32519     },
32520
32521     /**
32522      * Executes the specified function once for every Component in this ZIndexManager, passing each
32523      * Component as the only parameter. Returning false from the function will stop the iteration.
32524      * The components are passed to the function starting at the bottom and proceeding to the top.
32525      * @param {Function} fn The function to execute for each item
32526      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
32527      * is executed. Defaults to the current Component in the iteration.
32528      */
32529     eachBottomUp: function (fn, scope) {
32530         var comp,
32531             stack = this.zIndexStack,
32532             i, n;
32533
32534         for (i = 0, n = stack.length ; i < n; i++) {
32535             comp = stack[i];
32536             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32537                 return;
32538             }
32539         }
32540     },
32541
32542     /**
32543      * Executes the specified function once for every Component in this ZIndexManager, passing each
32544      * Component as the only parameter. Returning false from the function will stop the iteration.
32545      * The components are passed to the function starting at the top and proceeding to the bottom.
32546      * @param {Function} fn The function to execute for each item
32547      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
32548      * is executed. Defaults to the current Component in the iteration.
32549      */
32550     eachTopDown: function (fn, scope) {
32551         var comp,
32552             stack = this.zIndexStack,
32553             i;
32554
32555         for (i = stack.length ; i-- > 0; ) {
32556             comp = stack[i];
32557             if (comp.isComponent && fn.call(scope || comp, comp) === false) {
32558                 return;
32559             }
32560         }
32561     },
32562
32563     destroy: function() {
32564         this.each(function(c) {
32565             c.destroy();
32566         });
32567         delete this.zIndexStack;
32568         delete this.list;
32569         delete this.container;
32570         delete this.targetEl;
32571     }
32572 }, function() {
32573     /**
32574      * @class Ext.WindowManager
32575      * @extends Ext.ZIndexManager
32576      * <p>The default global floating Component group that is available automatically.</p>
32577      * <p>This manages instances of floating Components which were rendered programatically without
32578      * being added to a {@link Ext.container.Container Container}, and for floating Components which were added into non-floating Containers.</p>
32579      * <p><i>Floating</i> Containers create their own instance of ZIndexManager, and floating Components added at any depth below
32580      * there are managed by that ZIndexManager.</p>
32581      * @singleton
32582      */
32583     Ext.WindowManager = Ext.WindowMgr = new this();
32584 });
32585
32586 /**
32587  * @private
32588  * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
32589  * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
32590  * for its container.
32591  */
32592 Ext.define('Ext.layout.container.boxOverflow.None', {
32593     
32594     alternateClassName: 'Ext.layout.boxOverflow.None',
32595     
32596     constructor: function(layout, config) {
32597         this.layout = layout;
32598         Ext.apply(this, config || {});
32599     },
32600
32601     handleOverflow: Ext.emptyFn,
32602
32603     clearOverflow: Ext.emptyFn,
32604     
32605     onRemove: Ext.emptyFn,
32606
32607     /**
32608      * @private
32609      * Normalizes an item reference, string id or numerical index into a reference to the item
32610      * @param {Ext.Component/String/Number} item The item reference, id or index
32611      * @return {Ext.Component} The item
32612      */
32613     getItem: function(item) {
32614         return this.layout.owner.getComponent(item);
32615     },
32616     
32617     onRemove: Ext.emptyFn
32618 });
32619 /**
32620  * @class Ext.util.KeyMap
32621  * Handles mapping keys to actions for an element. One key map can be used for multiple actions.
32622  * The constructor accepts the same config object as defined by {@link #addBinding}.
32623  * If you bind a callback function to a KeyMap, anytime the KeyMap handles an expected key
32624  * combination it will call the function with this signature (if the match is a multi-key
32625  * combination the callback will still be called only once): (String key, Ext.EventObject e)
32626  * A KeyMap can also handle a string representation of keys. By default KeyMap starts enabled.<br />
32627  * Usage:
32628  <pre><code>
32629 // map one key by key code
32630 var map = new Ext.util.KeyMap("my-element", {
32631     key: 13, // or Ext.EventObject.ENTER
32632     fn: myHandler,
32633     scope: myObject
32634 });
32635
32636 // map multiple keys to one action by string
32637 var map = new Ext.util.KeyMap("my-element", {
32638     key: "a\r\n\t",
32639     fn: myHandler,
32640     scope: myObject
32641 });
32642
32643 // map multiple keys to multiple actions by strings and array of codes
32644 var map = new Ext.util.KeyMap("my-element", [
32645     {
32646         key: [10,13],
32647         fn: function(){ alert("Return was pressed"); }
32648     }, {
32649         key: "abc",
32650         fn: function(){ alert('a, b or c was pressed'); }
32651     }, {
32652         key: "\t",
32653         ctrl:true,
32654         shift:true,
32655         fn: function(){ alert('Control + shift + tab was pressed.'); }
32656     }
32657 ]);
32658 </code></pre>
32659  */
32660 Ext.define('Ext.util.KeyMap', {
32661     alternateClassName: 'Ext.KeyMap',
32662
32663     /**
32664      * Creates new KeyMap.
32665      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
32666      * @param {Object} binding The binding (see {@link #addBinding})
32667      * @param {String} [eventName="keydown"] The event to bind to
32668      */
32669     constructor: function(el, binding, eventName){
32670         var me = this;
32671
32672         Ext.apply(me, {
32673             el: Ext.get(el),
32674             eventName: eventName || me.eventName,
32675             bindings: []
32676         });
32677         if (binding) {
32678             me.addBinding(binding);
32679         }
32680         me.enable();
32681     },
32682
32683     eventName: 'keydown',
32684
32685     /**
32686      * Add a new binding to this KeyMap. The following config object properties are supported:
32687      * <pre>
32688 Property            Type             Description
32689 ----------          ---------------  ----------------------------------------------------------------------
32690 key                 String/Array     A single keycode or an array of keycodes to handle
32691 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)
32692 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)
32693 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)
32694 handler             Function         The function to call when KeyMap finds the expected key combination
32695 fn                  Function         Alias of handler (for backwards-compatibility)
32696 scope               Object           The scope of the callback function
32697 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.
32698 </pre>
32699      *
32700      * Usage:
32701      * <pre><code>
32702 // Create a KeyMap
32703 var map = new Ext.util.KeyMap(document, {
32704     key: Ext.EventObject.ENTER,
32705     fn: handleKey,
32706     scope: this
32707 });
32708
32709 //Add a new binding to the existing KeyMap later
32710 map.addBinding({
32711     key: 'abc',
32712     shift: true,
32713     fn: handleKey,
32714     scope: this
32715 });
32716 </code></pre>
32717      * @param {Object/Object[]} binding A single KeyMap config or an array of configs
32718      */
32719     addBinding : function(binding){
32720         if (Ext.isArray(binding)) {
32721             Ext.each(binding, this.addBinding, this);
32722             return;
32723         }
32724
32725         var keyCode = binding.key,
32726             processed = false,
32727             key,
32728             keys,
32729             keyString,
32730             i,
32731             len;
32732
32733         if (Ext.isString(keyCode)) {
32734             keys = [];
32735             keyString = keyCode.toUpperCase();
32736
32737             for (i = 0, len = keyString.length; i < len; ++i){
32738                 keys.push(keyString.charCodeAt(i));
32739             }
32740             keyCode = keys;
32741             processed = true;
32742         }
32743
32744         if (!Ext.isArray(keyCode)) {
32745             keyCode = [keyCode];
32746         }
32747
32748         if (!processed) {
32749             for (i = 0, len = keyCode.length; i < len; ++i) {
32750                 key = keyCode[i];
32751                 if (Ext.isString(key)) {
32752                     keyCode[i] = key.toUpperCase().charCodeAt(0);
32753                 }
32754             }
32755         }
32756
32757         this.bindings.push(Ext.apply({
32758             keyCode: keyCode
32759         }, binding));
32760     },
32761
32762     /**
32763      * Process any keydown events on the element
32764      * @private
32765      * @param {Ext.EventObject} event
32766      */
32767     handleKeyDown: function(event) {
32768         if (this.enabled) { //just in case
32769             var bindings = this.bindings,
32770                 i = 0,
32771                 len = bindings.length;
32772
32773             event = this.processEvent(event);
32774             for(; i < len; ++i){
32775                 this.processBinding(bindings[i], event);
32776             }
32777         }
32778     },
32779
32780     /**
32781      * Ugly hack to allow this class to be tested. Currently WebKit gives
32782      * no way to raise a key event properly with both
32783      * a) A keycode
32784      * b) The alt/ctrl/shift modifiers
32785      * So we have to simulate them here. Yuk!
32786      * This is a stub method intended to be overridden by tests.
32787      * More info: https://bugs.webkit.org/show_bug.cgi?id=16735
32788      * @private
32789      */
32790     processEvent: function(event){
32791         return event;
32792     },
32793
32794     /**
32795      * Process a particular binding and fire the handler if necessary.
32796      * @private
32797      * @param {Object} binding The binding information
32798      * @param {Ext.EventObject} event
32799      */
32800     processBinding: function(binding, event){
32801         if (this.checkModifiers(binding, event)) {
32802             var key = event.getKey(),
32803                 handler = binding.fn || binding.handler,
32804                 scope = binding.scope || this,
32805                 keyCode = binding.keyCode,
32806                 defaultEventAction = binding.defaultEventAction,
32807                 i,
32808                 len,
32809                 keydownEvent = new Ext.EventObjectImpl(event);
32810
32811
32812             for (i = 0, len = keyCode.length; i < len; ++i) {
32813                 if (key === keyCode[i]) {
32814                     if (handler.call(scope, key, event) !== true && defaultEventAction) {
32815                         keydownEvent[defaultEventAction]();
32816                     }
32817                     break;
32818                 }
32819             }
32820         }
32821     },
32822
32823     /**
32824      * Check if the modifiers on the event match those on the binding
32825      * @private
32826      * @param {Object} binding
32827      * @param {Ext.EventObject} event
32828      * @return {Boolean} True if the event matches the binding
32829      */
32830     checkModifiers: function(binding, e){
32831         var keys = ['shift', 'ctrl', 'alt'],
32832             i = 0,
32833             len = keys.length,
32834             val, key;
32835
32836         for (; i < len; ++i){
32837             key = keys[i];
32838             val = binding[key];
32839             if (!(val === undefined || (val === e[key + 'Key']))) {
32840                 return false;
32841             }
32842         }
32843         return true;
32844     },
32845
32846     /**
32847      * Shorthand for adding a single key listener
32848      * @param {Number/Number[]/Object} key Either the numeric key code, array of key codes or an object with the
32849      * following options:
32850      * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
32851      * @param {Function} fn The function to call
32852      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
32853      */
32854     on: function(key, fn, scope) {
32855         var keyCode, shift, ctrl, alt;
32856         if (Ext.isObject(key) && !Ext.isArray(key)) {
32857             keyCode = key.key;
32858             shift = key.shift;
32859             ctrl = key.ctrl;
32860             alt = key.alt;
32861         } else {
32862             keyCode = key;
32863         }
32864         this.addBinding({
32865             key: keyCode,
32866             shift: shift,
32867             ctrl: ctrl,
32868             alt: alt,
32869             fn: fn,
32870             scope: scope
32871         });
32872     },
32873
32874     /**
32875      * Returns true if this KeyMap is enabled
32876      * @return {Boolean}
32877      */
32878     isEnabled : function(){
32879         return this.enabled;
32880     },
32881
32882     /**
32883      * Enables this KeyMap
32884      */
32885     enable: function(){
32886         var me = this;
32887         
32888         if (!me.enabled) {
32889             me.el.on(me.eventName, me.handleKeyDown, me);
32890             me.enabled = true;
32891         }
32892     },
32893
32894     /**
32895      * Disable this KeyMap
32896      */
32897     disable: function(){
32898         var me = this;
32899         
32900         if (me.enabled) {
32901             me.el.removeListener(me.eventName, me.handleKeyDown, me);
32902             me.enabled = false;
32903         }
32904     },
32905
32906     /**
32907      * Convenience function for setting disabled/enabled by boolean.
32908      * @param {Boolean} disabled
32909      */
32910     setDisabled : function(disabled){
32911         if (disabled) {
32912             this.disable();
32913         } else {
32914             this.enable();
32915         }
32916     },
32917
32918     /**
32919      * Destroys the KeyMap instance and removes all handlers.
32920      * @param {Boolean} removeEl True to also remove the attached element
32921      */
32922     destroy: function(removeEl){
32923         var me = this;
32924
32925         me.bindings = [];
32926         me.disable();
32927         if (removeEl === true) {
32928             me.el.remove();
32929         }
32930         delete me.el;
32931     }
32932 });
32933 /**
32934  * @class Ext.util.ClickRepeater
32935  * @extends Ext.util.Observable
32936  *
32937  * A wrapper class which can be applied to any element. Fires a "click" event while the
32938  * mouse is pressed. The interval between firings may be specified in the config but
32939  * defaults to 20 milliseconds.
32940  *
32941  * Optionally, a CSS class may be applied to the element during the time it is pressed.
32942  *
32943  */
32944 Ext.define('Ext.util.ClickRepeater', {
32945     extend: 'Ext.util.Observable',
32946
32947     /**
32948      * Creates new ClickRepeater.
32949      * @param {String/HTMLElement/Ext.Element} el The element or its ID to listen on
32950      * @param {Object} config (optional) Config object.
32951      */
32952     constructor : function(el, config){
32953         this.el = Ext.get(el);
32954         this.el.unselectable();
32955
32956         Ext.apply(this, config);
32957
32958         this.addEvents(
32959         /**
32960          * @event mousedown
32961          * Fires when the mouse button is depressed.
32962          * @param {Ext.util.ClickRepeater} this
32963          * @param {Ext.EventObject} e
32964          */
32965         "mousedown",
32966         /**
32967          * @event click
32968          * Fires on a specified interval during the time the element is pressed.
32969          * @param {Ext.util.ClickRepeater} this
32970          * @param {Ext.EventObject} e
32971          */
32972         "click",
32973         /**
32974          * @event mouseup
32975          * Fires when the mouse key is released.
32976          * @param {Ext.util.ClickRepeater} this
32977          * @param {Ext.EventObject} e
32978          */
32979         "mouseup"
32980         );
32981
32982         if(!this.disabled){
32983             this.disabled = true;
32984             this.enable();
32985         }
32986
32987         // allow inline handler
32988         if(this.handler){
32989             this.on("click", this.handler,  this.scope || this);
32990         }
32991
32992         this.callParent();
32993     },
32994
32995     /**
32996      * @cfg {String/HTMLElement/Ext.Element} el The element to act as a button.
32997      */
32998
32999     /**
33000      * @cfg {String} pressedCls A CSS class name to be applied to the element while pressed.
33001      */
33002
33003     /**
33004      * @cfg {Boolean} accelerate True if autorepeating should start slowly and accelerate.
33005      * "interval" and "delay" are ignored.
33006      */
33007
33008     /**
33009      * @cfg {Number} interval The interval between firings of the "click" event. Default 20 ms.
33010      */
33011     interval : 20,
33012
33013     /**
33014      * @cfg {Number} delay The initial delay before the repeating event begins firing.
33015      * Similar to an autorepeat key delay.
33016      */
33017     delay: 250,
33018
33019     /**
33020      * @cfg {Boolean} preventDefault True to prevent the default click event
33021      */
33022     preventDefault : true,
33023     /**
33024      * @cfg {Boolean} stopDefault True to stop the default click event
33025      */
33026     stopDefault : false,
33027
33028     timer : 0,
33029
33030     /**
33031      * Enables the repeater and allows events to fire.
33032      */
33033     enable: function(){
33034         if(this.disabled){
33035             this.el.on('mousedown', this.handleMouseDown, this);
33036             if (Ext.isIE){
33037                 this.el.on('dblclick', this.handleDblClick, this);
33038             }
33039             if(this.preventDefault || this.stopDefault){
33040                 this.el.on('click', this.eventOptions, this);
33041             }
33042         }
33043         this.disabled = false;
33044     },
33045
33046     /**
33047      * Disables the repeater and stops events from firing.
33048      */
33049     disable: function(/* private */ force){
33050         if(force || !this.disabled){
33051             clearTimeout(this.timer);
33052             if(this.pressedCls){
33053                 this.el.removeCls(this.pressedCls);
33054             }
33055             Ext.getDoc().un('mouseup', this.handleMouseUp, this);
33056             this.el.removeAllListeners();
33057         }
33058         this.disabled = true;
33059     },
33060
33061     /**
33062      * Convenience function for setting disabled/enabled by boolean.
33063      * @param {Boolean} disabled
33064      */
33065     setDisabled: function(disabled){
33066         this[disabled ? 'disable' : 'enable']();
33067     },
33068
33069     eventOptions: function(e){
33070         if(this.preventDefault){
33071             e.preventDefault();
33072         }
33073         if(this.stopDefault){
33074             e.stopEvent();
33075         }
33076     },
33077
33078     // private
33079     destroy : function() {
33080         this.disable(true);
33081         Ext.destroy(this.el);
33082         this.clearListeners();
33083     },
33084
33085     handleDblClick : function(e){
33086         clearTimeout(this.timer);
33087         this.el.blur();
33088
33089         this.fireEvent("mousedown", this, e);
33090         this.fireEvent("click", this, e);
33091     },
33092
33093     // private
33094     handleMouseDown : function(e){
33095         clearTimeout(this.timer);
33096         this.el.blur();
33097         if(this.pressedCls){
33098             this.el.addCls(this.pressedCls);
33099         }
33100         this.mousedownTime = new Date();
33101
33102         Ext.getDoc().on("mouseup", this.handleMouseUp, this);
33103         this.el.on("mouseout", this.handleMouseOut, this);
33104
33105         this.fireEvent("mousedown", this, e);
33106         this.fireEvent("click", this, e);
33107
33108         // Do not honor delay or interval if acceleration wanted.
33109         if (this.accelerate) {
33110             this.delay = 400;
33111         }
33112
33113         // Re-wrap the event object in a non-shared object, so it doesn't lose its context if
33114         // the global shared EventObject gets a new Event put into it before the timer fires.
33115         e = new Ext.EventObjectImpl(e);
33116
33117         this.timer =  Ext.defer(this.click, this.delay || this.interval, this, [e]);
33118     },
33119
33120     // private
33121     click : function(e){
33122         this.fireEvent("click", this, e);
33123         this.timer =  Ext.defer(this.click, this.accelerate ?
33124             this.easeOutExpo(Ext.Date.getElapsed(this.mousedownTime),
33125                 400,
33126                 -390,
33127                 12000) :
33128             this.interval, this, [e]);
33129     },
33130
33131     easeOutExpo : function (t, b, c, d) {
33132         return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
33133     },
33134
33135     // private
33136     handleMouseOut : function(){
33137         clearTimeout(this.timer);
33138         if(this.pressedCls){
33139             this.el.removeCls(this.pressedCls);
33140         }
33141         this.el.on("mouseover", this.handleMouseReturn, this);
33142     },
33143
33144     // private
33145     handleMouseReturn : function(){
33146         this.el.un("mouseover", this.handleMouseReturn, this);
33147         if(this.pressedCls){
33148             this.el.addCls(this.pressedCls);
33149         }
33150         this.click();
33151     },
33152
33153     // private
33154     handleMouseUp : function(e){
33155         clearTimeout(this.timer);
33156         this.el.un("mouseover", this.handleMouseReturn, this);
33157         this.el.un("mouseout", this.handleMouseOut, this);
33158         Ext.getDoc().un("mouseup", this.handleMouseUp, this);
33159         if(this.pressedCls){
33160             this.el.removeCls(this.pressedCls);
33161         }
33162         this.fireEvent("mouseup", this, e);
33163     }
33164 });
33165
33166 /**
33167  * @class Ext.layout.component.Component
33168  * @extends Ext.layout.Layout
33169  *
33170  * This class is intended to be extended or created via the {@link Ext.Component#componentLayout layout}
33171  * configuration property.  See {@link Ext.Component#componentLayout} for additional details.
33172  *
33173  * @private
33174  */
33175 Ext.define('Ext.layout.component.Component', {
33176
33177     /* Begin Definitions */
33178
33179     extend: 'Ext.layout.Layout',
33180
33181     /* End Definitions */
33182
33183     type: 'component',
33184
33185     monitorChildren: true,
33186
33187     initLayout : function() {
33188         var me = this,
33189             owner = me.owner,
33190             ownerEl = owner.el;
33191
33192         if (!me.initialized) {
33193             if (owner.frameSize) {
33194                 me.frameSize = owner.frameSize;
33195             }
33196             else {
33197                 owner.frameSize = me.frameSize = {
33198                     top: 0,
33199                     left: 0,
33200                     bottom: 0,
33201                     right: 0
33202                 };
33203             }
33204         }
33205         me.callParent(arguments);
33206     },
33207
33208     beforeLayout : function(width, height, isSetSize, callingContainer) {
33209         this.callParent(arguments);
33210
33211         var me = this,
33212             owner = me.owner,
33213             ownerCt = owner.ownerCt,
33214             layout = owner.layout,
33215             isVisible = owner.isVisible(true),
33216             ownerElChild = owner.el.child,
33217             layoutCollection;
33218
33219         // Cache the size we began with so we can see if there has been any effect.
33220         me.previousComponentSize = me.lastComponentSize;
33221
33222         // Do not allow autoing of any dimensions which are fixed
33223         if (!isSetSize
33224             && ((!Ext.isNumber(width) && owner.isFixedWidth()) ||
33225                 (!Ext.isNumber(height) && owner.isFixedHeight()))
33226             // unless we are being told to do so by the ownerCt's layout
33227             && callingContainer && callingContainer !== ownerCt) {
33228             
33229             me.doContainerLayout();
33230             return false;
33231         }
33232
33233         // If an ownerCt is hidden, add my reference onto the layoutOnShow stack.  Set the needsLayout flag.
33234         // If the owner itself is a directly hidden floater, set the needsLayout object on that for when it is shown.
33235         if (!isVisible && (owner.hiddenAncestor || owner.floating)) {
33236             if (owner.hiddenAncestor) {
33237                 layoutCollection = owner.hiddenAncestor.layoutOnShow;
33238                 layoutCollection.remove(owner);
33239                 layoutCollection.add(owner);
33240             }
33241             owner.needsLayout = {
33242                 width: width,
33243                 height: height,
33244                 isSetSize: false
33245             };
33246         }
33247
33248         if (isVisible && this.needsLayout(width, height)) {
33249             return owner.beforeComponentLayout(width, height, isSetSize, callingContainer);
33250         }
33251         else {
33252             return false;
33253         }
33254     },
33255
33256     /**
33257     * Check if the new size is different from the current size and only
33258     * trigger a layout if it is necessary.
33259     * @param {Number} width The new width to set.
33260     * @param {Number} height The new height to set.
33261     */
33262     needsLayout : function(width, height) {
33263         var me = this,
33264             widthBeingChanged,
33265             heightBeingChanged;
33266             me.lastComponentSize = me.lastComponentSize || {
33267                 width: -Infinity,
33268                 height: -Infinity
33269             };
33270
33271         // If autoWidthing, or an explicitly different width is passed, then the width is being changed.
33272         widthBeingChanged  = !Ext.isDefined(width)  || me.lastComponentSize.width  !== width;
33273
33274         // If autoHeighting, or an explicitly different height is passed, then the height is being changed.
33275         heightBeingChanged = !Ext.isDefined(height) || me.lastComponentSize.height !== height;
33276
33277
33278         // isSizing flag added to prevent redundant layouts when going up the layout chain
33279         return !me.isSizing && (me.childrenChanged || widthBeingChanged || heightBeingChanged);
33280     },
33281
33282     /**
33283     * Set the size of any element supporting undefined, null, and values.
33284     * @param {Number} width The new width to set.
33285     * @param {Number} height The new height to set.
33286     */
33287     setElementSize: function(el, width, height) {
33288         if (width !== undefined && height !== undefined) {
33289             el.setSize(width, height);
33290         }
33291         else if (height !== undefined) {
33292             el.setHeight(height);
33293         }
33294         else if (width !== undefined) {
33295             el.setWidth(width);
33296         }
33297     },
33298
33299     /**
33300      * Returns the owner component's resize element.
33301      * @return {Ext.Element}
33302      */
33303      getTarget : function() {
33304          return this.owner.el;
33305      },
33306
33307     /**
33308      * <p>Returns the element into which rendering must take place. Defaults to the owner Component's encapsulating element.</p>
33309      * May be overridden in Component layout managers which implement an inner element.
33310      * @return {Ext.Element}
33311      */
33312     getRenderTarget : function() {
33313         return this.owner.el;
33314     },
33315
33316     /**
33317     * Set the size of the target element.
33318     * @param {Number} width The new width to set.
33319     * @param {Number} height The new height to set.
33320     */
33321     setTargetSize : function(width, height) {
33322         var me = this;
33323         me.setElementSize(me.owner.el, width, height);
33324
33325         if (me.owner.frameBody) {
33326             var targetInfo = me.getTargetInfo(),
33327                 padding = targetInfo.padding,
33328                 border = targetInfo.border,
33329                 frameSize = me.frameSize;
33330
33331             me.setElementSize(me.owner.frameBody,
33332                 Ext.isNumber(width) ? (width - frameSize.left - frameSize.right - padding.left - padding.right - border.left - border.right) : width,
33333                 Ext.isNumber(height) ? (height - frameSize.top - frameSize.bottom - padding.top - padding.bottom - border.top - border.bottom) : height
33334             );
33335         }
33336
33337         me.autoSized = {
33338             width: !Ext.isNumber(width),
33339             height: !Ext.isNumber(height)
33340         };
33341
33342         me.lastComponentSize = {
33343             width: width,
33344             height: height
33345         };
33346     },
33347
33348     getTargetInfo : function() {
33349         if (!this.targetInfo) {
33350             var target = this.getTarget(),
33351                 body = this.owner.getTargetEl();
33352
33353             this.targetInfo = {
33354                 padding: {
33355                     top: target.getPadding('t'),
33356                     right: target.getPadding('r'),
33357                     bottom: target.getPadding('b'),
33358                     left: target.getPadding('l')
33359                 },
33360                 border: {
33361                     top: target.getBorderWidth('t'),
33362                     right: target.getBorderWidth('r'),
33363                     bottom: target.getBorderWidth('b'),
33364                     left: target.getBorderWidth('l')
33365                 },
33366                 bodyMargin: {
33367                     top: body.getMargin('t'),
33368                     right: body.getMargin('r'),
33369                     bottom: body.getMargin('b'),
33370                     left: body.getMargin('l')
33371                 }
33372             };
33373         }
33374         return this.targetInfo;
33375     },
33376
33377     // Start laying out UP the ownerCt's layout when flagged to do so.
33378     doOwnerCtLayouts: function() {
33379         var owner = this.owner,
33380             ownerCt = owner.ownerCt,
33381             ownerCtComponentLayout, ownerCtContainerLayout,
33382             curSize = this.lastComponentSize,
33383             prevSize = this.previousComponentSize,
33384             widthChange  = (prevSize && curSize && Ext.isNumber(curSize.width )) ? curSize.width  !== prevSize.width  : true,
33385             heightChange = (prevSize && curSize && Ext.isNumber(curSize.height)) ? curSize.height !== prevSize.height : true;
33386
33387         // If size has not changed, do not inform upstream layouts
33388         if (!ownerCt || (!widthChange && !heightChange)) {
33389             return;
33390         }
33391
33392         ownerCtComponentLayout = ownerCt.componentLayout;
33393         ownerCtContainerLayout = ownerCt.layout;
33394
33395         if (!owner.floating && ownerCtComponentLayout && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
33396             if (!ownerCt.suspendLayout && ownerCtContainerLayout && !ownerCtContainerLayout.layoutBusy) {
33397
33398                 // If the owning Container may be adjusted in any of the the dimension which have changed, perform its Component layout
33399                 if (((widthChange && !ownerCt.isFixedWidth()) || (heightChange && !ownerCt.isFixedHeight()))) {
33400                     // Set the isSizing flag so that the upstream Container layout (called after a Component layout) can omit this component from sizing operations
33401                     this.isSizing = true;
33402                     ownerCt.doComponentLayout();
33403                     this.isSizing = false;
33404                 }
33405                 // Execute upstream Container layout
33406                 else if (ownerCtContainerLayout.bindToOwnerCtContainer === true) {
33407                     ownerCtContainerLayout.layout();
33408                 }
33409             }
33410         }
33411     },
33412
33413     doContainerLayout: function() {
33414         var me = this,
33415             owner = me.owner,
33416             ownerCt = owner.ownerCt,
33417             layout = owner.layout,
33418             ownerCtComponentLayout;
33419
33420         // Run the container layout if it exists (layout for child items)
33421         // **Unless automatic laying out is suspended, or the layout is currently running**
33422         if (!owner.suspendLayout && layout && layout.isLayout && !layout.layoutBusy && !layout.isAutoDock) {
33423             layout.layout();
33424         }
33425
33426         // Tell the ownerCt that it's child has changed and can be re-layed by ignoring the lastComponentSize cache.
33427         if (ownerCt && ownerCt.componentLayout) {
33428             ownerCtComponentLayout = ownerCt.componentLayout;
33429             if (!owner.floating && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
33430                 ownerCtComponentLayout.childrenChanged = true;
33431             }
33432         }
33433     },
33434
33435     afterLayout : function(width, height, isSetSize, layoutOwner) {
33436         this.doContainerLayout();
33437         this.owner.afterComponentLayout(width, height, isSetSize, layoutOwner);
33438     }
33439 });
33440
33441 /**
33442  * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
33443  * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
33444  * should not contain any HTML, otherwise it may not be measured correctly.
33445  *
33446  * The measurement works by copying the relevant CSS styles that can affect the font related display, 
33447  * then checking the size of an element that is auto-sized. Note that if the text is multi-lined, you must 
33448  * provide a **fixed width** when doing the measurement.
33449  *
33450  * If multiple measurements are being done on the same element, you create a new instance to initialize 
33451  * to avoid the overhead of copying the styles to the element repeatedly.
33452  */
33453 Ext.define('Ext.util.TextMetrics', {
33454     statics: {
33455         shared: null,
33456         /**
33457          * Measures the size of the specified text
33458          * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
33459          * that can affect the size of the rendered text
33460          * @param {String} text The text to measure
33461          * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
33462          * in order to accurately measure the text height
33463          * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
33464          */
33465         measure: function(el, text, fixedWidth){
33466             var me = this,
33467                 shared = me.shared;
33468             
33469             if(!shared){
33470                 shared = me.shared = new me(el, fixedWidth);
33471             }
33472             shared.bind(el);
33473             shared.setFixedWidth(fixedWidth || 'auto');
33474             return shared.getSize(text);
33475         },
33476         
33477         /**
33478           * Destroy the TextMetrics instance created by {@link #measure}.
33479           */
33480          destroy: function(){
33481              var me = this;
33482              Ext.destroy(me.shared);
33483              me.shared = null;
33484          }
33485     },
33486     
33487     /**
33488      * Creates new TextMetrics.
33489      * @param {String/HTMLElement/Ext.Element} bindTo The element or its ID to bind to.
33490      * @param {Number} fixedWidth (optional) A fixed width to apply to the measuring element.
33491      */
33492     constructor: function(bindTo, fixedWidth){
33493         var measure = this.measure = Ext.getBody().createChild({
33494             cls: 'x-textmetrics'
33495         });
33496         this.el = Ext.get(bindTo);
33497         
33498         measure.position('absolute');
33499         measure.setLeftTop(-1000, -1000);
33500         measure.hide();
33501
33502         if (fixedWidth) {
33503            measure.setWidth(fixedWidth);
33504         }
33505     },
33506     
33507     /**
33508      * Returns the size of the specified text based on the internal element's style and width properties
33509      * @param {String} text The text to measure
33510      * @return {Object} An object containing the text's size `{width: (width), height: (height)}`
33511      */
33512     getSize: function(text){
33513         var measure = this.measure,
33514             size;
33515         
33516         measure.update(text);
33517         size = measure.getSize();
33518         measure.update('');
33519         return size;
33520     },
33521     
33522     /**
33523      * Binds this TextMetrics instance to a new element
33524      * @param {String/HTMLElement/Ext.Element} el The element or its ID.
33525      */
33526     bind: function(el){
33527         var me = this;
33528         
33529         me.el = Ext.get(el);
33530         me.measure.setStyle(
33531             me.el.getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
33532         );
33533     },
33534     
33535     /**
33536      * Sets a fixed width on the internal measurement element.  If the text will be multiline, you have
33537      * to set a fixed width in order to accurately measure the text height.
33538      * @param {Number} width The width to set on the element
33539      */
33540      setFixedWidth : function(width){
33541          this.measure.setWidth(width);
33542      },
33543      
33544      /**
33545       * Returns the measured width of the specified text
33546       * @param {String} text The text to measure
33547       * @return {Number} width The width in pixels
33548       */
33549      getWidth : function(text){
33550          this.measure.dom.style.width = 'auto';
33551          return this.getSize(text).width;
33552      },
33553      
33554      /**
33555       * Returns the measured height of the specified text
33556       * @param {String} text The text to measure
33557       * @return {Number} height The height in pixels
33558       */
33559      getHeight : function(text){
33560          return this.getSize(text).height;
33561      },
33562      
33563      /**
33564       * Destroy this instance
33565       */
33566      destroy: function(){
33567          var me = this;
33568          me.measure.remove();
33569          delete me.el;
33570          delete me.measure;
33571      }
33572 }, function(){
33573     Ext.Element.addMethods({
33574         /**
33575          * Returns the width in pixels of the passed text, or the width of the text in this Element.
33576          * @param {String} text The text to measure. Defaults to the innerHTML of the element.
33577          * @param {Number} min (optional) The minumum value to return.
33578          * @param {Number} max (optional) The maximum value to return.
33579          * @return {Number} The text width in pixels.
33580          * @member Ext.Element
33581          */
33582         getTextWidth : function(text, min, max){
33583             return Ext.Number.constrain(Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width, min || 0, max || 1000000);
33584         }
33585     });
33586 });
33587
33588 /**
33589  * @class Ext.layout.container.boxOverflow.Scroller
33590  * @extends Ext.layout.container.boxOverflow.None
33591  * @private
33592  */
33593 Ext.define('Ext.layout.container.boxOverflow.Scroller', {
33594
33595     /* Begin Definitions */
33596
33597     extend: 'Ext.layout.container.boxOverflow.None',
33598     requires: ['Ext.util.ClickRepeater', 'Ext.Element'],
33599     alternateClassName: 'Ext.layout.boxOverflow.Scroller',
33600     mixins: {
33601         observable: 'Ext.util.Observable'
33602     },
33603     
33604     /* End Definitions */
33605
33606     /**
33607      * @cfg {Boolean} animateScroll
33608      * True to animate the scrolling of items within the layout (ignored if enableScroll is false)
33609      */
33610     animateScroll: false,
33611
33612     /**
33613      * @cfg {Number} scrollIncrement
33614      * The number of pixels to scroll by on scroller click
33615      */
33616     scrollIncrement: 20,
33617
33618     /**
33619      * @cfg {Number} wheelIncrement
33620      * The number of pixels to increment on mouse wheel scrolling.
33621      */
33622     wheelIncrement: 10,
33623
33624     /**
33625      * @cfg {Number} scrollRepeatInterval
33626      * Number of milliseconds between each scroll while a scroller button is held down
33627      */
33628     scrollRepeatInterval: 60,
33629
33630     /**
33631      * @cfg {Number} scrollDuration
33632      * Number of milliseconds that each scroll animation lasts
33633      */
33634     scrollDuration: 400,
33635
33636     /**
33637      * @cfg {String} beforeCtCls
33638      * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
33639      * which must always be present at the leftmost edge of the Container
33640      */
33641
33642     /**
33643      * @cfg {String} afterCtCls
33644      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
33645      * which must always be present at the rightmost edge of the Container
33646      */
33647
33648     /**
33649      * @cfg {String} [scrollerCls='x-box-scroller']
33650      * CSS class added to both scroller elements if enableScroll is used
33651      */
33652     scrollerCls: Ext.baseCSSPrefix + 'box-scroller',
33653
33654     /**
33655      * @cfg {String} beforeScrollerCls
33656      * CSS class added to the left scroller element if enableScroll is used
33657      */
33658
33659     /**
33660      * @cfg {String} afterScrollerCls
33661      * CSS class added to the right scroller element if enableScroll is used
33662      */
33663     
33664     constructor: function(layout, config) {
33665         this.layout = layout;
33666         Ext.apply(this, config || {});
33667         
33668         this.addEvents(
33669             /**
33670              * @event scroll
33671              * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller
33672              * @param {Number} newPosition The new position of the scroller
33673              * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false
33674              */
33675             'scroll'
33676         );
33677     },
33678     
33679     initCSSClasses: function() {
33680         var me = this,
33681         layout = me.layout;
33682
33683         if (!me.CSSinitialized) {
33684             me.beforeCtCls = me.beforeCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelBefore;
33685             me.afterCtCls  = me.afterCtCls  || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelAfter;
33686             me.beforeScrollerCls = me.beforeScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelBefore;
33687             me.afterScrollerCls  = me.afterScrollerCls  || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelAfter;
33688             me.CSSinitializes = true;
33689         }
33690     },
33691
33692     handleOverflow: function(calculations, targetSize) {
33693         var me = this,
33694             layout = me.layout,
33695             methodName = 'get' + layout.parallelPrefixCap,
33696             newSize = {};
33697
33698         me.initCSSClasses();
33699         me.callParent(arguments);
33700         this.createInnerElements();
33701         this.showScrollers();
33702         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
33703         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - (me.beforeCt[methodName]() + me.afterCt[methodName]());
33704         return { targetSize: newSize };
33705     },
33706
33707     /**
33708      * @private
33709      * Creates the beforeCt and afterCt elements if they have not already been created
33710      */
33711     createInnerElements: function() {
33712         var me = this,
33713             target = me.layout.getRenderTarget();
33714
33715         //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
33716         //special items such as scrollers or dropdown menu triggers
33717         if (!me.beforeCt) {
33718             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
33719             me.beforeCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.beforeCtCls}, 'before');
33720             me.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.afterCtCls},  'after');
33721             me.createWheelListener();
33722         }
33723     },
33724
33725     /**
33726      * @private
33727      * Sets up an listener to scroll on the layout's innerCt mousewheel event
33728      */
33729     createWheelListener: function() {
33730         this.layout.innerCt.on({
33731             scope     : this,
33732             mousewheel: function(e) {
33733                 e.stopEvent();
33734
33735                 this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
33736             }
33737         });
33738     },
33739
33740     /**
33741      * @private
33742      */
33743     clearOverflow: function() {
33744         this.hideScrollers();
33745     },
33746
33747     /**
33748      * @private
33749      * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
33750      * present. 
33751      */
33752     showScrollers: function() {
33753         this.createScrollers();
33754         this.beforeScroller.show();
33755         this.afterScroller.show();
33756         this.updateScrollButtons();
33757         
33758         this.layout.owner.addClsWithUI('scroller');
33759     },
33760
33761     /**
33762      * @private
33763      * Hides the scroller elements in the beforeCt and afterCt
33764      */
33765     hideScrollers: function() {
33766         if (this.beforeScroller != undefined) {
33767             this.beforeScroller.hide();
33768             this.afterScroller.hide();
33769             
33770             this.layout.owner.removeClsWithUI('scroller');
33771         }
33772     },
33773
33774     /**
33775      * @private
33776      * Creates the clickable scroller elements and places them into the beforeCt and afterCt
33777      */
33778     createScrollers: function() {
33779         if (!this.beforeScroller && !this.afterScroller) {
33780             var before = this.beforeCt.createChild({
33781                 cls: Ext.String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
33782             });
33783
33784             var after = this.afterCt.createChild({
33785                 cls: Ext.String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
33786             });
33787
33788             before.addClsOnOver(this.beforeScrollerCls + '-hover');
33789             after.addClsOnOver(this.afterScrollerCls + '-hover');
33790
33791             before.setVisibilityMode(Ext.Element.DISPLAY);
33792             after.setVisibilityMode(Ext.Element.DISPLAY);
33793
33794             this.beforeRepeater = Ext.create('Ext.util.ClickRepeater', before, {
33795                 interval: this.scrollRepeatInterval,
33796                 handler : this.scrollLeft,
33797                 scope   : this
33798             });
33799
33800             this.afterRepeater = Ext.create('Ext.util.ClickRepeater', after, {
33801                 interval: this.scrollRepeatInterval,
33802                 handler : this.scrollRight,
33803                 scope   : this
33804             });
33805
33806             /**
33807              * @property beforeScroller
33808              * @type Ext.Element
33809              * The left scroller element. Only created when needed.
33810              */
33811             this.beforeScroller = before;
33812
33813             /**
33814              * @property afterScroller
33815              * @type Ext.Element
33816              * The left scroller element. Only created when needed.
33817              */
33818             this.afterScroller = after;
33819         }
33820     },
33821
33822     /**
33823      * @private
33824      */
33825     destroy: function() {
33826         Ext.destroy(this.beforeRepeater, this.afterRepeater, this.beforeScroller, this.afterScroller, this.beforeCt, this.afterCt);
33827     },
33828
33829     /**
33830      * @private
33831      * Scrolls left or right by the number of pixels specified
33832      * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
33833      */
33834     scrollBy: function(delta, animate) {
33835         this.scrollTo(this.getScrollPosition() + delta, animate);
33836     },
33837
33838     /**
33839      * @private
33840      * @return {Object} Object passed to scrollTo when scrolling
33841      */
33842     getScrollAnim: function() {
33843         return {
33844             duration: this.scrollDuration, 
33845             callback: this.updateScrollButtons, 
33846             scope   : this
33847         };
33848     },
33849
33850     /**
33851      * @private
33852      * Enables or disables each scroller button based on the current scroll position
33853      */
33854     updateScrollButtons: function() {
33855         if (this.beforeScroller == undefined || this.afterScroller == undefined) {
33856             return;
33857         }
33858
33859         var beforeMeth = this.atExtremeBefore()  ? 'addCls' : 'removeCls',
33860             afterMeth  = this.atExtremeAfter() ? 'addCls' : 'removeCls',
33861             beforeCls  = this.beforeScrollerCls + '-disabled',
33862             afterCls   = this.afterScrollerCls  + '-disabled';
33863         
33864         this.beforeScroller[beforeMeth](beforeCls);
33865         this.afterScroller[afterMeth](afterCls);
33866         this.scrolling = false;
33867     },
33868
33869     /**
33870      * @private
33871      * Returns true if the innerCt scroll is already at its left-most point
33872      * @return {Boolean} True if already at furthest left point
33873      */
33874     atExtremeBefore: function() {
33875         return this.getScrollPosition() === 0;
33876     },
33877
33878     /**
33879      * @private
33880      * Scrolls to the left by the configured amount
33881      */
33882     scrollLeft: function() {
33883         this.scrollBy(-this.scrollIncrement, false);
33884     },
33885
33886     /**
33887      * @private
33888      * Scrolls to the right by the configured amount
33889      */
33890     scrollRight: function() {
33891         this.scrollBy(this.scrollIncrement, false);
33892     },
33893
33894     /**
33895      * Returns the current scroll position of the innerCt element
33896      * @return {Number} The current scroll position
33897      */
33898     getScrollPosition: function(){
33899         var layout = this.layout;
33900         return parseInt(layout.innerCt.dom['scroll' + layout.parallelBeforeCap], 10) || 0;
33901     },
33902
33903     /**
33904      * @private
33905      * Returns the maximum value we can scrollTo
33906      * @return {Number} The max scroll value
33907      */
33908     getMaxScrollPosition: function() {
33909         var layout = this.layout;
33910         return layout.innerCt.dom['scroll' + layout.parallelPrefixCap] - this.layout.innerCt['get' + layout.parallelPrefixCap]();
33911     },
33912
33913     /**
33914      * @private
33915      * Returns true if the innerCt scroll is already at its right-most point
33916      * @return {Boolean} True if already at furthest right point
33917      */
33918     atExtremeAfter: function() {
33919         return this.getScrollPosition() >= this.getMaxScrollPosition();
33920     },
33921
33922     /**
33923      * @private
33924      * Scrolls to the given position. Performs bounds checking.
33925      * @param {Number} position The position to scroll to. This is constrained.
33926      * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
33927      */
33928     scrollTo: function(position, animate) {
33929         var me = this,
33930             layout = me.layout,
33931             oldPosition = me.getScrollPosition(),
33932             newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());
33933
33934         if (newPosition != oldPosition && !me.scrolling) {
33935             if (animate == undefined) {
33936                 animate = me.animateScroll;
33937             }
33938
33939             layout.innerCt.scrollTo(layout.parallelBefore, newPosition, animate ? me.getScrollAnim() : false);
33940             if (animate) {
33941                 me.scrolling = true;
33942             } else {
33943                 me.scrolling = false;
33944                 me.updateScrollButtons();
33945             }
33946             
33947             me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);
33948         }
33949     },
33950
33951     /**
33952      * Scrolls to the given component.
33953      * @param {String/Number/Ext.Component} item The item to scroll to. Can be a numerical index, component id 
33954      * or a reference to the component itself.
33955      * @param {Boolean} animate True to animate the scrolling
33956      */
33957     scrollToItem: function(item, animate) {
33958         var me = this,
33959             layout = me.layout,
33960             visibility,
33961             box,
33962             newPos;
33963
33964         item = me.getItem(item);
33965         if (item != undefined) {
33966             visibility = this.getItemVisibility(item);
33967             if (!visibility.fullyVisible) {
33968                 box  = item.getBox(true, true);
33969                 newPos = box[layout.parallelPosition];
33970                 if (visibility.hiddenEnd) {
33971                     newPos -= (this.layout.innerCt['get' + layout.parallelPrefixCap]() - box[layout.parallelPrefix]);
33972                 }
33973                 this.scrollTo(newPos, animate);
33974             }
33975         }
33976     },
33977
33978     /**
33979      * @private
33980      * For a given item in the container, return an object with information on whether the item is visible
33981      * with the current innerCt scroll value.
33982      * @param {Ext.Component} item The item
33983      * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd
33984      */
33985     getItemVisibility: function(item) {
33986         var me          = this,
33987             box         = me.getItem(item).getBox(true, true),
33988             layout      = me.layout,
33989             itemStart   = box[layout.parallelPosition],
33990             itemEnd     = itemStart + box[layout.parallelPrefix],
33991             scrollStart = me.getScrollPosition(),
33992             scrollEnd   = scrollStart + layout.innerCt['get' + layout.parallelPrefixCap]();
33993
33994         return {
33995             hiddenStart : itemStart < scrollStart,
33996             hiddenEnd   : itemEnd > scrollEnd,
33997             fullyVisible: itemStart > scrollStart && itemEnd < scrollEnd
33998         };
33999     }
34000 });
34001 /**
34002  * @class Ext.util.Offset
34003  * @ignore
34004  */
34005 Ext.define('Ext.util.Offset', {
34006
34007     /* Begin Definitions */
34008
34009     statics: {
34010         fromObject: function(obj) {
34011             return new this(obj.x, obj.y);
34012         }
34013     },
34014
34015     /* End Definitions */
34016
34017     constructor: function(x, y) {
34018         this.x = (x != null && !isNaN(x)) ? x : 0;
34019         this.y = (y != null && !isNaN(y)) ? y : 0;
34020
34021         return this;
34022     },
34023
34024     copy: function() {
34025         return new Ext.util.Offset(this.x, this.y);
34026     },
34027
34028     copyFrom: function(p) {
34029         this.x = p.x;
34030         this.y = p.y;
34031     },
34032
34033     toString: function() {
34034         return "Offset[" + this.x + "," + this.y + "]";
34035     },
34036
34037     equals: function(offset) {
34038
34039         return (this.x == offset.x && this.y == offset.y);
34040     },
34041
34042     round: function(to) {
34043         if (!isNaN(to)) {
34044             var factor = Math.pow(10, to);
34045             this.x = Math.round(this.x * factor) / factor;
34046             this.y = Math.round(this.y * factor) / factor;
34047         } else {
34048             this.x = Math.round(this.x);
34049             this.y = Math.round(this.y);
34050         }
34051     },
34052
34053     isZero: function() {
34054         return this.x == 0 && this.y == 0;
34055     }
34056 });
34057
34058 /**
34059  * @class Ext.util.KeyNav
34060  * <p>Provides a convenient wrapper for normalized keyboard navigation.  KeyNav allows you to bind
34061  * navigation keys to function calls that will get called when the keys are pressed, providing an easy
34062  * way to implement custom navigation schemes for any UI component.</p>
34063  * <p>The following are all of the possible keys that can be implemented: enter, space, left, right, up, down, tab, esc,
34064  * pageUp, pageDown, del, backspace, home, end.  Usage:</p>
34065  <pre><code>
34066 var nav = new Ext.util.KeyNav("my-element", {
34067     "left" : function(e){
34068         this.moveLeft(e.ctrlKey);
34069     },
34070     "right" : function(e){
34071         this.moveRight(e.ctrlKey);
34072     },
34073     "enter" : function(e){
34074         this.save();
34075     },
34076     scope : this
34077 });
34078 </code></pre>
34079  */
34080 Ext.define('Ext.util.KeyNav', {
34081     
34082     alternateClassName: 'Ext.KeyNav',
34083     
34084     requires: ['Ext.util.KeyMap'],
34085     
34086     statics: {
34087         keyOptions: {
34088             left: 37,
34089             right: 39,
34090             up: 38,
34091             down: 40,
34092             space: 32,
34093             pageUp: 33,
34094             pageDown: 34,
34095             del: 46,
34096             backspace: 8,
34097             home: 36,
34098             end: 35,
34099             enter: 13,
34100             esc: 27,
34101             tab: 9
34102         }
34103     },
34104
34105     /**
34106      * Creates new KeyNav.
34107      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
34108      * @param {Object} config The config
34109      */
34110     constructor: function(el, config){
34111         this.setConfig(el, config || {});
34112     },
34113     
34114     /**
34115      * Sets up a configuration for the KeyNav.
34116      * @private
34117      * @param {String/HTMLElement/Ext.Element} el The element or its ID to bind to
34118      * @param {Object} config A configuration object as specified in the constructor.
34119      */
34120     setConfig: function(el, config) {
34121         if (this.map) {
34122             this.map.destroy();
34123         }
34124         
34125         var map = Ext.create('Ext.util.KeyMap', el, null, this.getKeyEvent('forceKeyDown' in config ? config.forceKeyDown : this.forceKeyDown)),
34126             keys = Ext.util.KeyNav.keyOptions,
34127             scope = config.scope || this,
34128             key;
34129         
34130         this.map = map;
34131         for (key in keys) {
34132             if (keys.hasOwnProperty(key)) {
34133                 if (config[key]) {
34134                     map.addBinding({
34135                         scope: scope,
34136                         key: keys[key],
34137                         handler: Ext.Function.bind(this.handleEvent, scope, [config[key]], true),
34138                         defaultEventAction: config.defaultEventAction || this.defaultEventAction
34139                     });
34140                 }
34141             }
34142         }
34143         
34144         map.disable();
34145         if (!config.disabled) {
34146             map.enable();
34147         }
34148     },
34149     
34150     /**
34151      * Method for filtering out the map argument
34152      * @private
34153      * @param {Ext.util.KeyMap} map
34154      * @param {Ext.EventObject} event
34155      * @param {Object} options Contains the handler to call
34156      */
34157     handleEvent: function(map, event, handler){
34158         return handler.call(this, event);
34159     },
34160     
34161     /**
34162      * @cfg {Boolean} disabled
34163      * True to disable this KeyNav instance.
34164      */
34165     disabled: false,
34166     
34167     /**
34168      * @cfg {String} defaultEventAction
34169      * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key.  Valid values are
34170      * {@link Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and
34171      * {@link Ext.EventObject#stopPropagation}.
34172      */
34173     defaultEventAction: "stopEvent",
34174     
34175     /**
34176      * @cfg {Boolean} forceKeyDown
34177      * Handle the keydown event instead of keypress.  KeyNav automatically does this for IE since
34178      * IE does not propagate special keys on keypress, but setting this to true will force other browsers to also
34179      * handle keydown instead of keypress.
34180      */
34181     forceKeyDown: false,
34182     
34183     /**
34184      * Destroy this KeyNav (this is the same as calling disable).
34185      * @param {Boolean} removeEl True to remove the element associated with this KeyNav.
34186      */
34187     destroy: function(removeEl){
34188         this.map.destroy(removeEl);
34189         delete this.map;
34190     },
34191
34192     /**
34193      * Enable this KeyNav
34194      */
34195     enable: function() {
34196         this.map.enable();
34197         this.disabled = false;
34198     },
34199
34200     /**
34201      * Disable this KeyNav
34202      */
34203     disable: function() {
34204         this.map.disable();
34205         this.disabled = true;
34206     },
34207     
34208     /**
34209      * Convenience function for setting disabled/enabled by boolean.
34210      * @param {Boolean} disabled
34211      */
34212     setDisabled : function(disabled){
34213         this.map.setDisabled(disabled);
34214         this.disabled = disabled;
34215     },
34216     
34217     /**
34218      * Determines the event to bind to listen for keys. Depends on the {@link #forceKeyDown} setting,
34219      * as well as the useKeyDown option on the EventManager.
34220      * @return {String} The type of event to listen for.
34221      */
34222     getKeyEvent: function(forceKeyDown){
34223         return (forceKeyDown || Ext.EventManager.useKeyDown) ? 'keydown' : 'keypress';
34224     }
34225 });
34226
34227 /**
34228  * @class Ext.fx.Queue
34229  * Animation Queue mixin to handle chaining and queueing by target.
34230  * @private
34231  */
34232
34233 Ext.define('Ext.fx.Queue', {
34234
34235     requires: ['Ext.util.HashMap'],
34236
34237     constructor: function() {
34238         this.targets = Ext.create('Ext.util.HashMap');
34239         this.fxQueue = {};
34240     },
34241
34242     // @private
34243     getFxDefaults: function(targetId) {
34244         var target = this.targets.get(targetId);
34245         if (target) {
34246             return target.fxDefaults;
34247         }
34248         return {};
34249     },
34250
34251     // @private
34252     setFxDefaults: function(targetId, obj) {
34253         var target = this.targets.get(targetId);
34254         if (target) {
34255             target.fxDefaults = Ext.apply(target.fxDefaults || {}, obj);
34256         }
34257     },
34258
34259     // @private
34260     stopAnimation: function(targetId) {
34261         var me = this,
34262             queue = me.getFxQueue(targetId),
34263             ln = queue.length;
34264         while (ln) {
34265             queue[ln - 1].end();
34266             ln--;
34267         }
34268     },
34269
34270     /**
34271      * @private
34272      * Returns current animation object if the element has any effects actively running or queued, else returns false.
34273      */
34274     getActiveAnimation: function(targetId) {
34275         var queue = this.getFxQueue(targetId);
34276         return (queue && !!queue.length) ? queue[0] : false;
34277     },
34278
34279     // @private
34280     hasFxBlock: function(targetId) {
34281         var queue = this.getFxQueue(targetId);
34282         return queue && queue[0] && queue[0].block;
34283     },
34284
34285     // @private get fx queue for passed target, create if needed.
34286     getFxQueue: function(targetId) {
34287         if (!targetId) {
34288             return false;
34289         }
34290         var me = this,
34291             queue = me.fxQueue[targetId],
34292             target = me.targets.get(targetId);
34293
34294         if (!target) {
34295             return false;
34296         }
34297
34298         if (!queue) {
34299             me.fxQueue[targetId] = [];
34300             // GarbageCollector will need to clean up Elements since they aren't currently observable
34301             if (target.type != 'element') {
34302                 target.target.on('destroy', function() {
34303                     me.fxQueue[targetId] = [];
34304                 });
34305             }
34306         }
34307         return me.fxQueue[targetId];
34308     },
34309
34310     // @private
34311     queueFx: function(anim) {
34312         var me = this,
34313             target = anim.target,
34314             queue, ln;
34315
34316         if (!target) {
34317             return;
34318         }
34319
34320         queue = me.getFxQueue(target.getId());
34321         ln = queue.length;
34322
34323         if (ln) {
34324             if (anim.concurrent) {
34325                 anim.paused = false;
34326             }
34327             else {
34328                 queue[ln - 1].on('afteranimate', function() {
34329                     anim.paused = false;
34330                 });
34331             }
34332         }
34333         else {
34334             anim.paused = false;
34335         }
34336         anim.on('afteranimate', function() {
34337             Ext.Array.remove(queue, anim);
34338             if (anim.remove) {
34339                 if (target.type == 'element') {
34340                     var el = Ext.get(target.id);
34341                     if (el) {
34342                         el.remove();
34343                     }
34344                 }
34345             }
34346         }, this);
34347         queue.push(anim);
34348     }
34349 });
34350 /**
34351  * @class Ext.fx.target.Target
34352
34353 This class specifies a generic target for an animation. It provides a wrapper around a
34354 series of different types of objects to allow for a generic animation API.
34355 A target can be a single object or a Composite object containing other objects that are 
34356 to be animated. This class and it's subclasses are generally not created directly, the 
34357 underlying animation will create the appropriate Ext.fx.target.Target object by passing 
34358 the instance to be animated.
34359
34360 The following types of objects can be animated:
34361
34362 - {@link Ext.fx.target.Component Components}
34363 - {@link Ext.fx.target.Element Elements}
34364 - {@link Ext.fx.target.Sprite Sprites}
34365
34366  * @markdown
34367  * @abstract
34368  */
34369 Ext.define('Ext.fx.target.Target', {
34370
34371     isAnimTarget: true,
34372
34373     /**
34374      * Creates new Target.
34375      * @param {Ext.Component/Ext.Element/Ext.draw.Sprite} target The object to be animated
34376      */
34377     constructor: function(target) {
34378         this.target = target;
34379         this.id = this.getId();
34380     },
34381     
34382     getId: function() {
34383         return this.target.id;
34384     }
34385 });
34386
34387 /**
34388  * @class Ext.fx.target.Sprite
34389  * @extends Ext.fx.target.Target
34390
34391 This class represents a animation target for a {@link Ext.draw.Sprite}. In general this class will not be
34392 created directly, the {@link Ext.draw.Sprite} will be passed to the animation and
34393 and the appropriate target will be created.
34394
34395  * @markdown
34396  */
34397
34398 Ext.define('Ext.fx.target.Sprite', {
34399
34400     /* Begin Definitions */
34401
34402     extend: 'Ext.fx.target.Target',
34403
34404     /* End Definitions */
34405
34406     type: 'draw',
34407
34408     getFromPrim: function(sprite, attr) {
34409         var o;
34410         if (attr == 'translate') {
34411             o = {
34412                 x: sprite.attr.translation.x || 0,
34413                 y: sprite.attr.translation.y || 0
34414             };
34415         }
34416         else if (attr == 'rotate') {
34417             o = {
34418                 degrees: sprite.attr.rotation.degrees || 0,
34419                 x: sprite.attr.rotation.x,
34420                 y: sprite.attr.rotation.y
34421             };
34422         }
34423         else {
34424             o = sprite.attr[attr];
34425         }
34426         return o;
34427     },
34428
34429     getAttr: function(attr, val) {
34430         return [[this.target, val != undefined ? val : this.getFromPrim(this.target, attr)]];
34431     },
34432
34433     setAttr: function(targetData) {
34434         var ln = targetData.length,
34435             spriteArr = [],
34436             attrs, attr, attrArr, attPtr, spritePtr, idx, value, i, j, x, y, ln2;
34437         for (i = 0; i < ln; i++) {
34438             attrs = targetData[i].attrs;
34439             for (attr in attrs) {
34440                 attrArr = attrs[attr];
34441                 ln2 = attrArr.length;
34442                 for (j = 0; j < ln2; j++) {
34443                     spritePtr = attrArr[j][0];
34444                     attPtr = attrArr[j][1];
34445                     if (attr === 'translate') {
34446                         value = {
34447                             x: attPtr.x,
34448                             y: attPtr.y
34449                         };
34450                     }
34451                     else if (attr === 'rotate') {
34452                         x = attPtr.x;
34453                         if (isNaN(x)) {
34454                             x = null;
34455                         }
34456                         y = attPtr.y;
34457                         if (isNaN(y)) {
34458                             y = null;
34459                         }
34460                         value = {
34461                             degrees: attPtr.degrees,
34462                             x: x,
34463                             y: y
34464                         };
34465                     }
34466                     else if (attr === 'width' || attr === 'height' || attr === 'x' || attr === 'y') {
34467                         value = parseFloat(attPtr);
34468                     }
34469                     else {
34470                         value = attPtr;
34471                     }
34472                     idx = Ext.Array.indexOf(spriteArr, spritePtr);
34473                     if (idx == -1) {
34474                         spriteArr.push([spritePtr, {}]);
34475                         idx = spriteArr.length - 1;
34476                     }
34477                     spriteArr[idx][1][attr] = value;
34478                 }
34479             }
34480         }
34481         ln = spriteArr.length;
34482         for (i = 0; i < ln; i++) {
34483             spritePtr = spriteArr[i];
34484             spritePtr[0].setAttributes(spritePtr[1]);
34485         }
34486         this.target.redraw();
34487     }
34488 });
34489
34490 /**
34491  * @class Ext.fx.target.CompositeSprite
34492  * @extends Ext.fx.target.Sprite
34493
34494 This class represents a animation target for a {@link Ext.draw.CompositeSprite}. It allows
34495 each {@link Ext.draw.Sprite} in the group to be animated as a whole. In general this class will not be
34496 created directly, the {@link Ext.draw.CompositeSprite} will be passed to the animation and
34497 and the appropriate target will be created.
34498
34499  * @markdown
34500  */
34501
34502 Ext.define('Ext.fx.target.CompositeSprite', {
34503
34504     /* Begin Definitions */
34505
34506     extend: 'Ext.fx.target.Sprite',
34507
34508     /* End Definitions */
34509
34510     getAttr: function(attr, val) {
34511         var out = [],
34512             target = this.target;
34513         target.each(function(sprite) {
34514             out.push([sprite, val != undefined ? val : this.getFromPrim(sprite, attr)]);
34515         }, this);
34516         return out;
34517     }
34518 });
34519
34520 /**
34521  * @class Ext.fx.target.Component
34522  * @extends Ext.fx.target.Target
34523  * 
34524  * This class represents a animation target for a {@link Ext.Component}. In general this class will not be
34525  * created directly, the {@link Ext.Component} will be passed to the animation and
34526  * and the appropriate target will be created.
34527  */
34528 Ext.define('Ext.fx.target.Component', {
34529
34530     /* Begin Definitions */
34531    
34532     extend: 'Ext.fx.target.Target',
34533     
34534     /* End Definitions */
34535
34536     type: 'component',
34537
34538     // Methods to call to retrieve unspecified "from" values from a target Component
34539     getPropMethod: {
34540         top: function() {
34541             return this.getPosition(true)[1];
34542         },
34543         left: function() {
34544             return this.getPosition(true)[0];
34545         },
34546         x: function() {
34547             return this.getPosition()[0];
34548         },
34549         y: function() {
34550             return this.getPosition()[1];
34551         },
34552         height: function() {
34553             return this.getHeight();
34554         },
34555         width: function() {
34556             return this.getWidth();
34557         },
34558         opacity: function() {
34559             return this.el.getStyle('opacity');
34560         }
34561     },
34562
34563     compMethod: {
34564         top: 'setPosition',
34565         left: 'setPosition',
34566         x: 'setPagePosition',
34567         y: 'setPagePosition',
34568         height: 'setSize',
34569         width: 'setSize',
34570         opacity: 'setOpacity'
34571     },
34572
34573     // Read the named attribute from the target Component. Use the defined getter for the attribute
34574     getAttr: function(attr, val) {
34575         return [[this.target, val !== undefined ? val : this.getPropMethod[attr].call(this.target)]];
34576     },
34577
34578     setAttr: function(targetData, isFirstFrame, isLastFrame) {
34579         var me = this,
34580             target = me.target,
34581             ln = targetData.length,
34582             attrs, attr, o, i, j, meth, targets, left, top, w, h;
34583         for (i = 0; i < ln; i++) {
34584             attrs = targetData[i].attrs;
34585             for (attr in attrs) {
34586                 targets = attrs[attr].length;
34587                 meth = {
34588                     setPosition: {},
34589                     setPagePosition: {},
34590                     setSize: {},
34591                     setOpacity: {}
34592                 };
34593                 for (j = 0; j < targets; j++) {
34594                     o = attrs[attr][j];
34595                     // We REALLY want a single function call, so push these down to merge them: eg
34596                     // meth.setPagePosition.target = <targetComponent>
34597                     // meth.setPagePosition['x'] = 100
34598                     // meth.setPagePosition['y'] = 100
34599                     meth[me.compMethod[attr]].target = o[0];
34600                     meth[me.compMethod[attr]][attr] = o[1];
34601                 }
34602                 if (meth.setPosition.target) {
34603                     o = meth.setPosition;
34604                     left = (o.left === undefined) ? undefined : parseInt(o.left, 10);
34605                     top = (o.top === undefined) ? undefined : parseInt(o.top, 10);
34606                     o.target.setPosition(left, top);
34607                 }
34608                 if (meth.setPagePosition.target) {
34609                     o = meth.setPagePosition;
34610                     o.target.setPagePosition(o.x, o.y);
34611                 }
34612                 if (meth.setSize.target && meth.setSize.target.el) {
34613                     o = meth.setSize;
34614                     // Dimensions not being animated MUST NOT be autosized. They must remain at current value.
34615                     w = (o.width === undefined) ? o.target.getWidth() : parseInt(o.width, 10);
34616                     h = (o.height === undefined) ? o.target.getHeight() : parseInt(o.height, 10);
34617
34618                     // Only set the size of the Component on the last frame, or if the animation was
34619                     // configured with dynamic: true.
34620                     // In other cases, we just set the target element size.
34621                     // This will result in either clipping if animating a reduction in size, or the revealing of
34622                     // the inner elements of the Component if animating an increase in size.
34623                     // Component's animate function initially resizes to the larger size before resizing the
34624                     // outer element to clip the contents.
34625                     if (isLastFrame || me.dynamic) {
34626                         o.target.componentLayout.childrenChanged = true;
34627
34628                         // Flag if we are being called by an animating layout: use setCalculatedSize
34629                         if (me.layoutAnimation) {
34630                             o.target.setCalculatedSize(w, h);
34631                         } else {
34632                             o.target.setSize(w, h);
34633                         }
34634                     }
34635                     else {
34636                         o.target.el.setSize(w, h);
34637                     }
34638                 }
34639                 if (meth.setOpacity.target) {
34640                     o = meth.setOpacity;
34641                     o.target.el.setStyle('opacity', o.opacity);
34642                 }
34643             }
34644         }
34645     }
34646 });
34647
34648 /**
34649  * @class Ext.fx.CubicBezier
34650  * @ignore
34651  */
34652 Ext.define('Ext.fx.CubicBezier', {
34653
34654     /* Begin Definitions */
34655
34656     singleton: true,
34657
34658     /* End Definitions */
34659
34660     cubicBezierAtTime: function(t, p1x, p1y, p2x, p2y, duration) {
34661         var cx = 3 * p1x,
34662             bx = 3 * (p2x - p1x) - cx,
34663             ax = 1 - cx - bx,
34664             cy = 3 * p1y,
34665             by = 3 * (p2y - p1y) - cy,
34666             ay = 1 - cy - by;
34667         function sampleCurveX(t) {
34668             return ((ax * t + bx) * t + cx) * t;
34669         }
34670         function solve(x, epsilon) {
34671             var t = solveCurveX(x, epsilon);
34672             return ((ay * t + by) * t + cy) * t;
34673         }
34674         function solveCurveX(x, epsilon) {
34675             var t0, t1, t2, x2, d2, i;
34676             for (t2 = x, i = 0; i < 8; i++) {
34677                 x2 = sampleCurveX(t2) - x;
34678                 if (Math.abs(x2) < epsilon) {
34679                     return t2;
34680                 }
34681                 d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
34682                 if (Math.abs(d2) < 1e-6) {
34683                     break;
34684                 }
34685                 t2 = t2 - x2 / d2;
34686             }
34687             t0 = 0;
34688             t1 = 1;
34689             t2 = x;
34690             if (t2 < t0) {
34691                 return t0;
34692             }
34693             if (t2 > t1) {
34694                 return t1;
34695             }
34696             while (t0 < t1) {
34697                 x2 = sampleCurveX(t2);
34698                 if (Math.abs(x2 - x) < epsilon) {
34699                     return t2;
34700                 }
34701                 if (x > x2) {
34702                     t0 = t2;
34703                 } else {
34704                     t1 = t2;
34705                 }
34706                 t2 = (t1 - t0) / 2 + t0;
34707             }
34708             return t2;
34709         }
34710         return solve(t, 1 / (200 * duration));
34711     },
34712
34713     cubicBezier: function(x1, y1, x2, y2) {
34714         var fn = function(pos) {
34715             return Ext.fx.CubicBezier.cubicBezierAtTime(pos, x1, y1, x2, y2, 1);
34716         };
34717         fn.toCSS3 = function() {
34718             return 'cubic-bezier(' + [x1, y1, x2, y2].join(',') + ')';
34719         };
34720         fn.reverse = function() {
34721             return Ext.fx.CubicBezier.cubicBezier(1 - x2, 1 - y2, 1 - x1, 1 - y1);
34722         };
34723         return fn;
34724     }
34725 });
34726 /**
34727  * Represents an RGB color and provides helper functions get
34728  * color components in HSL color space.
34729  */
34730 Ext.define('Ext.draw.Color', {
34731
34732     /* Begin Definitions */
34733
34734     /* End Definitions */
34735
34736     colorToHexRe: /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/,
34737     rgbRe: /\s*rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
34738     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*/,
34739
34740     /**
34741      * @cfg {Number} lightnessFactor
34742      *
34743      * The default factor to compute the lighter or darker color. Defaults to 0.2.
34744      */
34745     lightnessFactor: 0.2,
34746
34747     /**
34748      * Creates new Color.
34749      * @param {Number} red Red component (0..255)
34750      * @param {Number} green Green component (0..255)
34751      * @param {Number} blue Blue component (0..255)
34752      */
34753     constructor : function(red, green, blue) {
34754         var me = this,
34755             clamp = Ext.Number.constrain;
34756         me.r = clamp(red, 0, 255);
34757         me.g = clamp(green, 0, 255);
34758         me.b = clamp(blue, 0, 255);
34759     },
34760
34761     /**
34762      * Get the red component of the color, in the range 0..255.
34763      * @return {Number}
34764      */
34765     getRed: function() {
34766         return this.r;
34767     },
34768
34769     /**
34770      * Get the green component of the color, in the range 0..255.
34771      * @return {Number}
34772      */
34773     getGreen: function() {
34774         return this.g;
34775     },
34776
34777     /**
34778      * Get the blue component of the color, in the range 0..255.
34779      * @return {Number}
34780      */
34781     getBlue: function() {
34782         return this.b;
34783     },
34784
34785     /**
34786      * Get the RGB values.
34787      * @return {Number[]}
34788      */
34789     getRGB: function() {
34790         var me = this;
34791         return [me.r, me.g, me.b];
34792     },
34793
34794     /**
34795      * Get the equivalent HSL components of the color.
34796      * @return {Number[]}
34797      */
34798     getHSL: function() {
34799         var me = this,
34800             r = me.r / 255,
34801             g = me.g / 255,
34802             b = me.b / 255,
34803             max = Math.max(r, g, b),
34804             min = Math.min(r, g, b),
34805             delta = max - min,
34806             h,
34807             s = 0,
34808             l = 0.5 * (max + min);
34809
34810         // min==max means achromatic (hue is undefined)
34811         if (min != max) {
34812             s = (l < 0.5) ? delta / (max + min) : delta / (2 - max - min);
34813             if (r == max) {
34814                 h = 60 * (g - b) / delta;
34815             } else if (g == max) {
34816                 h = 120 + 60 * (b - r) / delta;
34817             } else {
34818                 h = 240 + 60 * (r - g) / delta;
34819             }
34820             if (h < 0) {
34821                 h += 360;
34822             }
34823             if (h >= 360) {
34824                 h -= 360;
34825             }
34826         }
34827         return [h, s, l];
34828     },
34829
34830     /**
34831      * Return a new color that is lighter than this color.
34832      * @param {Number} factor Lighter factor (0..1), default to 0.2
34833      * @return Ext.draw.Color
34834      */
34835     getLighter: function(factor) {
34836         var hsl = this.getHSL();
34837         factor = factor || this.lightnessFactor;
34838         hsl[2] = Ext.Number.constrain(hsl[2] + factor, 0, 1);
34839         return this.fromHSL(hsl[0], hsl[1], hsl[2]);
34840     },
34841
34842     /**
34843      * Return a new color that is darker than this color.
34844      * @param {Number} factor Darker factor (0..1), default to 0.2
34845      * @return Ext.draw.Color
34846      */
34847     getDarker: function(factor) {
34848         factor = factor || this.lightnessFactor;
34849         return this.getLighter(-factor);
34850     },
34851
34852     /**
34853      * Return the color in the hex format, i.e. '#rrggbb'.
34854      * @return {String}
34855      */
34856     toString: function() {
34857         var me = this,
34858             round = Math.round,
34859             r = round(me.r).toString(16),
34860             g = round(me.g).toString(16),
34861             b = round(me.b).toString(16);
34862         r = (r.length == 1) ? '0' + r : r;
34863         g = (g.length == 1) ? '0' + g : g;
34864         b = (b.length == 1) ? '0' + b : b;
34865         return ['#', r, g, b].join('');
34866     },
34867
34868     /**
34869      * Convert a color to hexadecimal format.
34870      *
34871      * **Note:** This method is both static and instance.
34872      *
34873      * @param {String/String[]} color The color value (i.e 'rgb(255, 255, 255)', 'color: #ffffff').
34874      * Can also be an Array, in this case the function handles the first member.
34875      * @returns {String} The color in hexadecimal format.
34876      * @static
34877      */
34878     toHex: function(color) {
34879         if (Ext.isArray(color)) {
34880             color = color[0];
34881         }
34882         if (!Ext.isString(color)) {
34883             return '';
34884         }
34885         if (color.substr(0, 1) === '#') {
34886             return color;
34887         }
34888         var digits = this.colorToHexRe.exec(color);
34889
34890         if (Ext.isArray(digits)) {
34891             var red = parseInt(digits[2], 10),
34892                 green = parseInt(digits[3], 10),
34893                 blue = parseInt(digits[4], 10),
34894                 rgb = blue | (green << 8) | (red << 16);
34895             return digits[1] + '#' + ("000000" + rgb.toString(16)).slice(-6);
34896         }
34897         else {
34898             return '';
34899         }
34900     },
34901
34902     /**
34903      * Parse the string and create a new color.
34904      *
34905      * Supported formats: '#rrggbb', '#rgb', and 'rgb(r,g,b)'.
34906      *
34907      * If the string is not recognized, an undefined will be returned instead.
34908      *
34909      * **Note:** This method is both static and instance.
34910      *
34911      * @param {String} str Color in string.
34912      * @returns Ext.draw.Color
34913      * @static
34914      */
34915     fromString: function(str) {
34916         var values, r, g, b,
34917             parse = parseInt;
34918
34919         if ((str.length == 4 || str.length == 7) && str.substr(0, 1) === '#') {
34920             values = str.match(this.hexRe);
34921             if (values) {
34922                 r = parse(values[1], 16) >> 0;
34923                 g = parse(values[2], 16) >> 0;
34924                 b = parse(values[3], 16) >> 0;
34925                 if (str.length == 4) {
34926                     r += (r * 16);
34927                     g += (g * 16);
34928                     b += (b * 16);
34929                 }
34930             }
34931         }
34932         else {
34933             values = str.match(this.rgbRe);
34934             if (values) {
34935                 r = values[1];
34936                 g = values[2];
34937                 b = values[3];
34938             }
34939         }
34940
34941         return (typeof r == 'undefined') ? undefined : Ext.create('Ext.draw.Color', r, g, b);
34942     },
34943
34944     /**
34945      * Returns the gray value (0 to 255) of the color.
34946      *
34947      * The gray value is calculated using the formula r*0.3 + g*0.59 + b*0.11.
34948      *
34949      * @returns {Number}
34950      */
34951     getGrayscale: function() {
34952         // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
34953         return this.r * 0.3 + this.g * 0.59 + this.b * 0.11;
34954     },
34955
34956     /**
34957      * Create a new color based on the specified HSL values.
34958      *
34959      * **Note:** This method is both static and instance.
34960      *
34961      * @param {Number} h Hue component (0..359)
34962      * @param {Number} s Saturation component (0..1)
34963      * @param {Number} l Lightness component (0..1)
34964      * @returns Ext.draw.Color
34965      * @static
34966      */
34967     fromHSL: function(h, s, l) {
34968         var C, X, m, i, rgb = [],
34969             abs = Math.abs,
34970             floor = Math.floor;
34971
34972         if (s == 0 || h == null) {
34973             // achromatic
34974             rgb = [l, l, l];
34975         }
34976         else {
34977             // http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
34978             // C is the chroma
34979             // X is the second largest component
34980             // m is the lightness adjustment
34981             h /= 60;
34982             C = s * (1 - abs(2 * l - 1));
34983             X = C * (1 - abs(h - 2 * floor(h / 2) - 1));
34984             m = l - C / 2;
34985             switch (floor(h)) {
34986                 case 0:
34987                     rgb = [C, X, 0];
34988                     break;
34989                 case 1:
34990                     rgb = [X, C, 0];
34991                     break;
34992                 case 2:
34993                     rgb = [0, C, X];
34994                     break;
34995                 case 3:
34996                     rgb = [0, X, C];
34997                     break;
34998                 case 4:
34999                     rgb = [X, 0, C];
35000                     break;
35001                 case 5:
35002                     rgb = [C, 0, X];
35003                     break;
35004             }
35005             rgb = [rgb[0] + m, rgb[1] + m, rgb[2] + m];
35006         }
35007         return Ext.create('Ext.draw.Color', rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);
35008     }
35009 }, function() {
35010     var prototype = this.prototype;
35011
35012     //These functions are both static and instance. TODO: find a more elegant way of copying them
35013     this.addStatics({
35014         fromHSL: function() {
35015             return prototype.fromHSL.apply(prototype, arguments);
35016         },
35017         fromString: function() {
35018             return prototype.fromString.apply(prototype, arguments);
35019         },
35020         toHex: function() {
35021             return prototype.toHex.apply(prototype, arguments);
35022         }
35023     });
35024 });
35025
35026 /**
35027  * @class Ext.dd.StatusProxy
35028  * A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair.  This is the
35029  * default drag proxy used by all Ext.dd components.
35030  */
35031 Ext.define('Ext.dd.StatusProxy', {
35032     animRepair: false,
35033
35034     /**
35035      * Creates new StatusProxy.
35036      * @param {Object} config (optional) Config object.
35037      */
35038     constructor: function(config){
35039         Ext.apply(this, config);
35040         this.id = this.id || Ext.id();
35041         this.proxy = Ext.createWidget('component', {
35042             floating: true,
35043             stateful: false,
35044             id: this.id,
35045             html: '<div class="' + Ext.baseCSSPrefix + 'dd-drop-icon"></div>' +
35046                   '<div class="' + Ext.baseCSSPrefix + 'dd-drag-ghost"></div>',
35047             cls: Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed,
35048             shadow: !config || config.shadow !== false,
35049             renderTo: document.body
35050         });
35051
35052         this.el = this.proxy.el;
35053         this.el.show();
35054         this.el.setVisibilityMode(Ext.Element.VISIBILITY);
35055         this.el.hide();
35056
35057         this.ghost = Ext.get(this.el.dom.childNodes[1]);
35058         this.dropStatus = this.dropNotAllowed;
35059     },
35060     /**
35061      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
35062      * The CSS class to apply to the status element when drop is allowed.
35063      */
35064     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
35065     /**
35066      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
35067      * The CSS class to apply to the status element when drop is not allowed.
35068      */
35069     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
35070
35071     /**
35072      * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
35073      * over the current target element.
35074      * @param {String} cssClass The css class for the new drop status indicator image
35075      */
35076     setStatus : function(cssClass){
35077         cssClass = cssClass || this.dropNotAllowed;
35078         if(this.dropStatus != cssClass){
35079             this.el.replaceCls(this.dropStatus, cssClass);
35080             this.dropStatus = cssClass;
35081         }
35082     },
35083
35084     /**
35085      * Resets the status indicator to the default dropNotAllowed value
35086      * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
35087      */
35088     reset : function(clearGhost){
35089         this.el.dom.className = Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed;
35090         this.dropStatus = this.dropNotAllowed;
35091         if(clearGhost){
35092             this.ghost.update("");
35093         }
35094     },
35095
35096     /**
35097      * Updates the contents of the ghost element
35098      * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
35099      * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
35100      */
35101     update : function(html){
35102         if(typeof html == "string"){
35103             this.ghost.update(html);
35104         }else{
35105             this.ghost.update("");
35106             html.style.margin = "0";
35107             this.ghost.dom.appendChild(html);
35108         }
35109         var el = this.ghost.dom.firstChild;
35110         if(el){
35111             Ext.fly(el).setStyle('float', 'none');
35112         }
35113     },
35114
35115     /**
35116      * Returns the underlying proxy {@link Ext.Layer}
35117      * @return {Ext.Layer} el
35118     */
35119     getEl : function(){
35120         return this.el;
35121     },
35122
35123     /**
35124      * Returns the ghost element
35125      * @return {Ext.Element} el
35126      */
35127     getGhost : function(){
35128         return this.ghost;
35129     },
35130
35131     /**
35132      * Hides the proxy
35133      * @param {Boolean} clear True to reset the status and clear the ghost contents, false to preserve them
35134      */
35135     hide : function(clear) {
35136         this.proxy.hide();
35137         if (clear) {
35138             this.reset(true);
35139         }
35140     },
35141
35142     /**
35143      * Stops the repair animation if it's currently running
35144      */
35145     stop : function(){
35146         if(this.anim && this.anim.isAnimated && this.anim.isAnimated()){
35147             this.anim.stop();
35148         }
35149     },
35150
35151     /**
35152      * Displays this proxy
35153      */
35154     show : function() {
35155         this.proxy.show();
35156         this.proxy.toFront();
35157     },
35158
35159     /**
35160      * Force the Layer to sync its shadow and shim positions to the element
35161      */
35162     sync : function(){
35163         this.proxy.el.sync();
35164     },
35165
35166     /**
35167      * Causes the proxy to return to its position of origin via an animation.  Should be called after an
35168      * invalid drop operation by the item being dragged.
35169      * @param {Number[]} xy The XY position of the element ([x, y])
35170      * @param {Function} callback The function to call after the repair is complete.
35171      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
35172      */
35173     repair : function(xy, callback, scope){
35174         this.callback = callback;
35175         this.scope = scope;
35176         if (xy && this.animRepair !== false) {
35177             this.el.addCls(Ext.baseCSSPrefix + 'dd-drag-repair');
35178             this.el.hideUnders(true);
35179             this.anim = this.el.animate({
35180                 duration: this.repairDuration || 500,
35181                 easing: 'ease-out',
35182                 to: {
35183                     x: xy[0],
35184                     y: xy[1]
35185                 },
35186                 stopAnimation: true,
35187                 callback: this.afterRepair,
35188                 scope: this
35189             });
35190         } else {
35191             this.afterRepair();
35192         }
35193     },
35194
35195     // private
35196     afterRepair : function(){
35197         this.hide(true);
35198         if(typeof this.callback == "function"){
35199             this.callback.call(this.scope || this);
35200         }
35201         this.callback = null;
35202         this.scope = null;
35203     },
35204
35205     destroy: function(){
35206         Ext.destroy(this.ghost, this.proxy, this.el);
35207     }
35208 });
35209 /**
35210  * A custom drag proxy implementation specific to {@link Ext.panel.Panel}s. This class
35211  * is primarily used internally for the Panel's drag drop implementation, and
35212  * should never need to be created directly.
35213  * @private
35214  */
35215 Ext.define('Ext.panel.Proxy', {
35216
35217     alternateClassName: 'Ext.dd.PanelProxy',
35218
35219     /**
35220      * Creates new panel proxy.
35221      * @param {Ext.panel.Panel} panel The {@link Ext.panel.Panel} to proxy for
35222      * @param {Object} [config] Config object
35223      */
35224     constructor: function(panel, config){
35225         /**
35226          * @property panel
35227          * @type Ext.panel.Panel
35228          */
35229         this.panel = panel;
35230         this.id = this.panel.id +'-ddproxy';
35231         Ext.apply(this, config);
35232     },
35233
35234     /**
35235      * @cfg {Boolean} insertProxy
35236      * True to insert a placeholder proxy element while dragging the panel, false to drag with no proxy.
35237      * Most Panels are not absolute positioned and therefore we need to reserve this space.
35238      */
35239     insertProxy: true,
35240
35241     // private overrides
35242     setStatus: Ext.emptyFn,
35243     reset: Ext.emptyFn,
35244     update: Ext.emptyFn,
35245     stop: Ext.emptyFn,
35246     sync: Ext.emptyFn,
35247
35248     /**
35249      * Gets the proxy's element
35250      * @return {Ext.Element} The proxy's element
35251      */
35252     getEl: function(){
35253         return this.ghost.el;
35254     },
35255
35256     /**
35257      * Gets the proxy's ghost Panel
35258      * @return {Ext.panel.Panel} The proxy's ghost Panel
35259      */
35260     getGhost: function(){
35261         return this.ghost;
35262     },
35263
35264     /**
35265      * Gets the proxy element. This is the element that represents where the
35266      * Panel was before we started the drag operation.
35267      * @return {Ext.Element} The proxy's element
35268      */
35269     getProxy: function(){
35270         return this.proxy;
35271     },
35272
35273     /**
35274      * Hides the proxy
35275      */
35276     hide : function(){
35277         if (this.ghost) {
35278             if (this.proxy) {
35279                 this.proxy.remove();
35280                 delete this.proxy;
35281             }
35282
35283             // Unghost the Panel, do not move the Panel to where the ghost was
35284             this.panel.unghost(null, false);
35285             delete this.ghost;
35286         }
35287     },
35288
35289     /**
35290      * Shows the proxy
35291      */
35292     show: function(){
35293         if (!this.ghost) {
35294             var panelSize = this.panel.getSize();
35295             this.panel.el.setVisibilityMode(Ext.Element.DISPLAY);
35296             this.ghost = this.panel.ghost();
35297             if (this.insertProxy) {
35298                 // bc Panels aren't absolute positioned we need to take up the space
35299                 // of where the panel previously was
35300                 this.proxy = this.panel.el.insertSibling({cls: Ext.baseCSSPrefix + 'panel-dd-spacer'});
35301                 this.proxy.setSize(panelSize);
35302             }
35303         }
35304     },
35305
35306     // private
35307     repair: function(xy, callback, scope) {
35308         this.hide();
35309         if (typeof callback == "function") {
35310             callback.call(scope || this);
35311         }
35312     },
35313
35314     /**
35315      * Moves the proxy to a different position in the DOM.  This is typically
35316      * called while dragging the Panel to keep the proxy sync'd to the Panel's
35317      * location.
35318      * @param {HTMLElement} parentNode The proxy's parent DOM node
35319      * @param {HTMLElement} [before] The sibling node before which the
35320      * proxy should be inserted (defaults to the parent's last child if not
35321      * specified)
35322      */
35323     moveProxy : function(parentNode, before){
35324         if (this.proxy) {
35325             parentNode.insertBefore(this.proxy.dom, before);
35326         }
35327     }
35328 });
35329 /**
35330  * @class Ext.layout.component.AbstractDock
35331  * @extends Ext.layout.component.Component
35332  * @private
35333  * This ComponentLayout handles docking for Panels. It takes care of panels that are
35334  * part of a ContainerLayout that sets this Panel's size and Panels that are part of
35335  * an AutoContainerLayout in which this panel get his height based of the CSS or
35336  * or its content.
35337  */
35338
35339 Ext.define('Ext.layout.component.AbstractDock', {
35340
35341     /* Begin Definitions */
35342
35343     extend: 'Ext.layout.component.Component',
35344
35345     /* End Definitions */
35346
35347     type: 'dock',
35348
35349     /**
35350      * @private
35351      * @property autoSizing
35352      * @type Boolean
35353      * This flag is set to indicate this layout may have an autoHeight/autoWidth.
35354      */
35355     autoSizing: true,
35356
35357     beforeLayout: function() {
35358         var returnValue = this.callParent(arguments);
35359         if (returnValue !== false && (!this.initializedBorders || this.childrenChanged) && (!this.owner.border || this.owner.manageBodyBorders)) {
35360             this.handleItemBorders();
35361             this.initializedBorders = true;
35362         }
35363         return returnValue;
35364     },
35365     
35366     handleItemBorders: function() {
35367         var owner = this.owner,
35368             body = owner.body,
35369             docked = this.getLayoutItems(),
35370             borders = {
35371                 top: [],
35372                 right: [],
35373                 bottom: [],
35374                 left: []
35375             },
35376             oldBorders = this.borders,
35377             opposites = {
35378                 top: 'bottom',
35379                 right: 'left',
35380                 bottom: 'top',
35381                 left: 'right'
35382             },
35383             i, ln, item, dock, side;
35384
35385         for (i = 0, ln = docked.length; i < ln; i++) {
35386             item = docked[i];
35387             dock = item.dock;
35388             
35389             if (item.ignoreBorderManagement) {
35390                 continue;
35391             }
35392             
35393             if (!borders[dock].satisfied) {
35394                 borders[dock].push(item);
35395                 borders[dock].satisfied = true;
35396             }
35397             
35398             if (!borders.top.satisfied && opposites[dock] !== 'top') {
35399                 borders.top.push(item);
35400             }
35401             if (!borders.right.satisfied && opposites[dock] !== 'right') {
35402                 borders.right.push(item);
35403             }            
35404             if (!borders.bottom.satisfied && opposites[dock] !== 'bottom') {
35405                 borders.bottom.push(item);
35406             }            
35407             if (!borders.left.satisfied && opposites[dock] !== 'left') {
35408                 borders.left.push(item);
35409             }
35410         }
35411
35412         if (oldBorders) {
35413             for (side in oldBorders) {
35414                 if (oldBorders.hasOwnProperty(side)) {
35415                     ln = oldBorders[side].length;
35416                     if (!owner.manageBodyBorders) {
35417                         for (i = 0; i < ln; i++) {
35418                             oldBorders[side][i].removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
35419                         }
35420                         if (!oldBorders[side].satisfied && !owner.bodyBorder) {
35421                             body.removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
35422                         }                    
35423                     }
35424                     else if (oldBorders[side].satisfied) {
35425                         body.setStyle('border-' + side + '-width', '');
35426                     }
35427                 }
35428             }
35429         }
35430                 
35431         for (side in borders) {
35432             if (borders.hasOwnProperty(side)) {
35433                 ln = borders[side].length;
35434                 if (!owner.manageBodyBorders) {
35435                     for (i = 0; i < ln; i++) {
35436                         borders[side][i].addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
35437                     }
35438                     if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) {
35439                         body.addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);                   
35440                     }                    
35441                 }
35442                 else if (borders[side].satisfied) {
35443                     body.setStyle('border-' + side + '-width', '1px');
35444                 }
35445             }
35446         }
35447         
35448         this.borders = borders;
35449     },
35450     
35451     /**
35452      * @protected
35453      * @param {Ext.Component} owner The Panel that owns this DockLayout
35454      * @param {Ext.Element} target The target in which we are going to render the docked items
35455      * @param {Array} args The arguments passed to the ComponentLayout.layout method
35456      */
35457     onLayout: function(width, height) {
35458         if (this.onLayout_running) {
35459             return;
35460         }
35461         this.onLayout_running = true;
35462         var me = this,
35463             owner = me.owner,
35464             body = owner.body,
35465             layout = owner.layout,
35466             target = me.getTarget(),
35467             autoWidth = false,
35468             autoHeight = false,
35469             padding, border, frameSize;
35470
35471         // We start of by resetting all the layouts info
35472         var info = me.info = {
35473             boxes: [],
35474             size: {
35475                 width: width,
35476                 height: height
35477             },
35478             bodyBox: {}
35479         };
35480         // Clear isAutoDock flag
35481         delete layout.isAutoDock;
35482
35483         Ext.applyIf(info, me.getTargetInfo());
35484
35485         // We need to bind to the ownerCt whenever we do not have a user set height or width.
35486         if (owner && owner.ownerCt && owner.ownerCt.layout && owner.ownerCt.layout.isLayout) {
35487             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
35488                 owner.ownerCt.layout.bindToOwnerCtComponent = true;
35489             }
35490             else {
35491                 owner.ownerCt.layout.bindToOwnerCtComponent = false;
35492             }
35493         }
35494
35495         // Determine if we have an autoHeight or autoWidth.
35496         if (height == null || width == null) {
35497             padding = info.padding;
35498             border = info.border;
35499             frameSize = me.frameSize;
35500
35501             // Auto-everything, clear out any style height/width and read from css
35502             if ((height == null) && (width == null)) {
35503                 autoHeight = true;
35504                 autoWidth = true;
35505                 me.setTargetSize(null);
35506                 me.setBodyBox({width: null, height: null});
35507             }
35508             // Auto-height
35509             else if (height == null) {
35510                 autoHeight = true;
35511                 // Clear any sizing that we already set in a previous layout
35512                 me.setTargetSize(width);
35513                 me.setBodyBox({width: width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right, height: null});
35514             // Auto-width
35515             }
35516             else {
35517                 autoWidth = true;
35518                 // Clear any sizing that we already set in a previous layout
35519                 me.setTargetSize(null, height);
35520                 me.setBodyBox({width: null, height: height - padding.top - padding.bottom - border.top - border.bottom - frameSize.top - frameSize.bottom});
35521             }
35522
35523             // Run the container
35524             if (layout && layout.isLayout) {
35525                 // Auto-Sized so have the container layout notify the component layout.
35526                 layout.bindToOwnerCtComponent = true;
35527                 // Set flag so we don't do a redundant container layout
35528                 layout.isAutoDock = layout.autoSize !== true;
35529                 layout.layout();
35530
35531                 // If this is an autosized container layout, then we must compensate for a
35532                 // body that is being autosized.  We do not want to adjust the body's size
35533                 // to accommodate the dock items, but rather we will want to adjust the
35534                 // target's size.
35535                 //
35536                 // This is necessary because, particularly in a Box layout, all child items
35537                 // are set with absolute dimensions that are not flexible to the size of its
35538                 // innerCt/target.  So once they are laid out, they are sized for good. By
35539                 // shrinking the body box to accommodate dock items, we're merely cutting off
35540                 // parts of the body.  Not good.  Instead, the target's size should expand
35541                 // to fit the dock items in.  This is valid because the target container is
35542                 // suppose to be autosized to fit everything accordingly.
35543                 info.autoSizedCtLayout = layout.autoSize === true;
35544                 info.autoHeight = autoHeight;
35545                 info.autoWidth = autoWidth;
35546             }
35547
35548             // The dockItems method will add all the top and bottom docked items height
35549             // to the info.panelSize height. That's why we have to call setSize after
35550             // we dock all the items to actually set the panel's width and height.
35551             // We have to do this because the panel body and docked items will be position
35552             // absolute which doesn't stretch the panel.
35553             me.dockItems();
35554             me.setTargetSize(info.size.width, info.size.height);
35555         }
35556         else {
35557             me.setTargetSize(width, height);
35558             me.dockItems();
35559         }
35560         me.callParent(arguments);
35561         this.onLayout_running = false;
35562     },
35563
35564     /**
35565      * @protected
35566      * This method will first update all the information about the docked items,
35567      * body dimensions and position, the panel's total size. It will then
35568      * set all these values on the docked items and panel body.
35569      * @param {Array} items Array containing all the docked items
35570      * @param {Boolean} autoBoxes Set this to true if the Panel is part of an
35571      * AutoContainerLayout
35572      */
35573     dockItems : function() {
35574         this.calculateDockBoxes();
35575
35576         // Both calculateAutoBoxes and calculateSizedBoxes are changing the
35577         // information about the body, panel size, and boxes for docked items
35578         // inside a property called info.
35579         var info = this.info,
35580             autoWidth = info.autoWidth,
35581             autoHeight = info.autoHeight,
35582             boxes = info.boxes,
35583             ln = boxes.length,
35584             dock, i, item;
35585
35586         // We are going to loop over all the boxes that were calculated
35587         // and set the position of each item the box belongs to.
35588         for (i = 0; i < ln; i++) {
35589             dock = boxes[i];
35590             item = dock.item;
35591             item.setPosition(dock.x, dock.y);
35592             if ((autoWidth || autoHeight) && item.layout && item.layout.isLayout) {
35593                 // Auto-Sized so have the container layout notify the component layout.
35594                 item.layout.bindToOwnerCtComponent = true;
35595             }
35596         }
35597
35598         // Don't adjust body width/height if the target is using an auto container layout.
35599         // But, we do want to adjust the body size if the container layout is auto sized.
35600         if (!info.autoSizedCtLayout) {
35601             if (autoWidth) {
35602                 info.bodyBox.width = null;
35603             }
35604             if (autoHeight) {
35605                 info.bodyBox.height = null;
35606             }
35607         }
35608
35609         // If the bodyBox has been adjusted because of the docked items
35610         // we will update the dimensions and position of the panel's body.
35611         this.setBodyBox(info.bodyBox);
35612     },
35613
35614     /**
35615      * @protected
35616      * This method will set up some initial information about the panel size and bodybox
35617      * and then loop over all the items you pass it to take care of stretching, aligning,
35618      * dock position and all calculations involved with adjusting the body box.
35619      * @param {Array} items Array containing all the docked items we have to layout
35620      */
35621     calculateDockBoxes : function() {
35622         if (this.calculateDockBoxes_running) {
35623             // [AbstractDock#calculateDockBoxes] attempted to run again while it was already running
35624             return;
35625         }
35626         this.calculateDockBoxes_running = true;
35627         // We want to use the Panel's el width, and the Panel's body height as the initial
35628         // size we are going to use in calculateDockBoxes. We also want to account for
35629         // the border of the panel.
35630         var me = this,
35631             target = me.getTarget(),
35632             items = me.getLayoutItems(),
35633             owner = me.owner,
35634             bodyEl = owner.body,
35635             info = me.info,
35636             autoWidth = info.autoWidth,
35637             autoHeight = info.autoHeight,
35638             size = info.size,
35639             ln = items.length,
35640             padding = info.padding,
35641             border = info.border,
35642             frameSize = me.frameSize,
35643             item, i, box, rect;
35644
35645         // If this Panel is inside an AutoContainerLayout, we will base all the calculations
35646         // around the height of the body and the width of the panel.
35647         if (autoHeight) {
35648             size.height = bodyEl.getHeight() + padding.top + border.top + padding.bottom + border.bottom + frameSize.top + frameSize.bottom;
35649         }
35650         else {
35651             size.height = target.getHeight();
35652         }
35653         if (autoWidth) {
35654             size.width = bodyEl.getWidth() + padding.left + border.left + padding.right + border.right + frameSize.left + frameSize.right;
35655         }
35656         else {
35657             size.width = target.getWidth();
35658         }
35659
35660         info.bodyBox = {
35661             x: padding.left + frameSize.left,
35662             y: padding.top + frameSize.top,
35663             width: size.width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right,
35664             height: size.height - border.top - padding.top - border.bottom - padding.bottom - frameSize.top - frameSize.bottom
35665         };
35666
35667         // Loop over all the docked items
35668         for (i = 0; i < ln; i++) {
35669             item = items[i];
35670             // The initBox method will take care of stretching and alignment
35671             // In some cases it will also layout the dock items to be able to
35672             // get a width or height measurement
35673             box = me.initBox(item);
35674
35675             if (autoHeight === true) {
35676                 box = me.adjustAutoBox(box, i);
35677             }
35678             else {
35679                 box = me.adjustSizedBox(box, i);
35680             }
35681
35682             // Save our box. This allows us to loop over all docked items and do all
35683             // calculations first. Then in one loop we will actually size and position
35684             // all the docked items that have changed.
35685             info.boxes.push(box);
35686         }
35687         this.calculateDockBoxes_running = false;
35688     },
35689
35690     /**
35691      * @protected
35692      * This method will adjust the position of the docked item and adjust the body box
35693      * accordingly.
35694      * @param {Object} box The box containing information about the width and height
35695      * of this docked item
35696      * @param {Number} index The index position of this docked item
35697      * @return {Object} The adjusted box
35698      */
35699     adjustSizedBox : function(box, index) {
35700         var bodyBox = this.info.bodyBox,
35701             frameSize = this.frameSize,
35702             info = this.info,
35703             padding = info.padding,
35704             pos = box.type,
35705             border = info.border;
35706
35707         switch (pos) {
35708             case 'top':
35709                 box.y = bodyBox.y;
35710                 break;
35711
35712             case 'left':
35713                 box.x = bodyBox.x;
35714                 break;
35715
35716             case 'bottom':
35717                 box.y = (bodyBox.y + bodyBox.height) - box.height;
35718                 break;
35719
35720             case 'right':
35721                 box.x = (bodyBox.x + bodyBox.width) - box.width;
35722                 break;
35723         }
35724
35725         if (box.ignoreFrame) {
35726             if (pos == 'bottom') {
35727                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
35728             }
35729             else {
35730                 box.y -= (frameSize.top + padding.top + border.top);
35731             }
35732             if (pos == 'right') {
35733                 box.x += (frameSize.right + padding.right + border.right);
35734             }
35735             else {
35736                 box.x -= (frameSize.left + padding.left + border.left);
35737             }
35738         }
35739
35740         // If this is not an overlaying docked item, we have to adjust the body box
35741         if (!box.overlay) {
35742             switch (pos) {
35743                 case 'top':
35744                     bodyBox.y += box.height;
35745                     bodyBox.height -= box.height;
35746                     break;
35747
35748                 case 'left':
35749                     bodyBox.x += box.width;
35750                     bodyBox.width -= box.width;
35751                     break;
35752
35753                 case 'bottom':
35754                     bodyBox.height -= box.height;
35755                     break;
35756
35757                 case 'right':
35758                     bodyBox.width -= box.width;
35759                     break;
35760             }
35761         }
35762         return box;
35763     },
35764
35765     /**
35766      * @protected
35767      * This method will adjust the position of the docked item inside an AutoContainerLayout
35768      * and adjust the body box accordingly.
35769      * @param {Object} box The box containing information about the width and height
35770      * of this docked item
35771      * @param {Number} index The index position of this docked item
35772      * @return {Object} The adjusted box
35773      */
35774     adjustAutoBox : function (box, index) {
35775         var info = this.info,
35776             owner = this.owner,
35777             bodyBox = info.bodyBox,
35778             size = info.size,
35779             boxes = info.boxes,
35780             boxesLn = boxes.length,
35781             pos = box.type,
35782             frameSize = this.frameSize,
35783             padding = info.padding,
35784             border = info.border,
35785             autoSizedCtLayout = info.autoSizedCtLayout,
35786             ln = (boxesLn < index) ? boxesLn : index,
35787             i, adjustBox;
35788
35789         if (pos == 'top' || pos == 'bottom') {
35790             // This can affect the previously set left and right and bottom docked items
35791             for (i = 0; i < ln; i++) {
35792                 adjustBox = boxes[i];
35793                 if (adjustBox.stretched && adjustBox.type == 'left' || adjustBox.type == 'right') {
35794                     adjustBox.height += box.height;
35795                 }
35796                 else if (adjustBox.type == 'bottom') {
35797                     adjustBox.y += box.height;
35798                 }
35799             }
35800         }
35801
35802         switch (pos) {
35803             case 'top':
35804                 box.y = bodyBox.y;
35805                 if (!box.overlay) {
35806                     bodyBox.y += box.height;
35807                     if (info.autoHeight) {
35808                         size.height += box.height;
35809                     } else {
35810                         bodyBox.height -= box.height;
35811                     }
35812                 }
35813                 break;
35814
35815             case 'bottom':
35816                 if (!box.overlay) {
35817                     if (info.autoHeight) {
35818                         size.height += box.height;
35819                     } else {
35820                         bodyBox.height -= box.height;
35821                     }
35822                 }
35823                 box.y = (bodyBox.y + bodyBox.height);
35824                 break;
35825
35826             case 'left':
35827                 box.x = bodyBox.x;
35828                 if (!box.overlay) {
35829                     bodyBox.x += box.width;
35830                     if (info.autoWidth) {
35831                         size.width += box.width;
35832                     } else {
35833                         bodyBox.width -= box.width;
35834                     }
35835                 }
35836                 break;
35837
35838             case 'right':
35839                 if (!box.overlay) {
35840                     if (info.autoWidth) {
35841                         size.width += box.width;
35842                     } else {
35843                         bodyBox.width -= box.width;
35844                     }
35845                 }
35846                 box.x = (bodyBox.x + bodyBox.width);
35847                 break;
35848         }
35849
35850         if (box.ignoreFrame) {
35851             if (pos == 'bottom') {
35852                 box.y += (frameSize.bottom + padding.bottom + border.bottom);
35853             }
35854             else {
35855                 box.y -= (frameSize.top + padding.top + border.top);
35856             }
35857             if (pos == 'right') {
35858                 box.x += (frameSize.right + padding.right + border.right);
35859             }
35860             else {
35861                 box.x -= (frameSize.left + padding.left + border.left);
35862             }
35863         }
35864         return box;
35865     },
35866
35867     /**
35868      * @protected
35869      * This method will create a box object, with a reference to the item, the type of dock
35870      * (top, left, bottom, right). It will also take care of stretching and aligning of the
35871      * docked items.
35872      * @param {Ext.Component} item The docked item we want to initialize the box for
35873      * @return {Object} The initial box containing width and height and other useful information
35874      */
35875     initBox : function(item) {
35876         var me = this,
35877             bodyBox = me.info.bodyBox,
35878             horizontal = (item.dock == 'top' || item.dock == 'bottom'),
35879             owner = me.owner,
35880             frameSize = me.frameSize,
35881             info = me.info,
35882             padding = info.padding,
35883             border = info.border,
35884             box = {
35885                 item: item,
35886                 overlay: item.overlay,
35887                 type: item.dock,
35888                 offsets: Ext.Element.parseBox(item.offsets || {}),
35889                 ignoreFrame: item.ignoreParentFrame
35890             };
35891         // First we are going to take care of stretch and align properties for all four dock scenarios.
35892         if (item.stretch !== false) {
35893             box.stretched = true;
35894             if (horizontal) {
35895                 box.x = bodyBox.x + box.offsets.left;
35896                 box.width = bodyBox.width - (box.offsets.left + box.offsets.right);
35897                 if (box.ignoreFrame) {
35898                     box.width += (frameSize.left + frameSize.right + border.left + border.right + padding.left + padding.right);
35899                 }
35900                 item.setCalculatedSize(box.width - item.el.getMargin('lr'), undefined, owner);
35901             }
35902             else {
35903                 box.y = bodyBox.y + box.offsets.top;
35904                 box.height = bodyBox.height - (box.offsets.bottom + box.offsets.top);
35905                 if (box.ignoreFrame) {
35906                     box.height += (frameSize.top + frameSize.bottom + border.top + border.bottom + padding.top + padding.bottom);
35907                 }
35908                 item.setCalculatedSize(undefined, box.height - item.el.getMargin('tb'), owner);
35909
35910                 // At this point IE will report the left/right-docked toolbar as having a width equal to the
35911                 // container's full width. Forcing a repaint kicks it into shape so it reports the correct width.
35912                 if (!Ext.supports.ComputedStyle) {
35913                     item.el.repaint();
35914                 }
35915             }
35916         }
35917         else {
35918             item.doComponentLayout();
35919             box.width = item.getWidth() - (box.offsets.left + box.offsets.right);
35920             box.height = item.getHeight() - (box.offsets.bottom + box.offsets.top);
35921             box.y += box.offsets.top;
35922             if (horizontal) {
35923                 box.x = (item.align == 'right') ? bodyBox.width - box.width : bodyBox.x;
35924                 box.x += box.offsets.left;
35925             }
35926         }
35927
35928         // If we haven't calculated the width or height of the docked item yet
35929         // do so, since we need this for our upcoming calculations
35930         if (box.width === undefined) {
35931             box.width = item.getWidth() + item.el.getMargin('lr');
35932         }
35933         if (box.height === undefined) {
35934             box.height = item.getHeight() + item.el.getMargin('tb');
35935         }
35936
35937         return box;
35938     },
35939
35940     /**
35941      * @protected
35942      * Returns an array containing all the <b>visible</b> docked items inside this layout's owner Panel
35943      * @return {Array} An array containing all the <b>visible</b> docked items of the Panel
35944      */
35945     getLayoutItems : function() {
35946         var it = this.owner.getDockedItems(),
35947             ln = it.length,
35948             i = 0,
35949             result = [];
35950         for (; i < ln; i++) {
35951             if (it[i].isVisible(true)) {
35952                 result.push(it[i]);
35953             }
35954         }
35955         return result;
35956     },
35957
35958     /**
35959      * @protected
35960      * Render the top and left docked items before any existing DOM nodes in our render target,
35961      * and then render the right and bottom docked items after. This is important, for such things
35962      * as tab stops and ARIA readers, that the DOM nodes are in a meaningful order.
35963      * Our collection of docked items will already be ordered via Panel.getDockedItems().
35964      */
35965     renderItems: function(items, target) {
35966         var cns = target.dom.childNodes,
35967             cnsLn = cns.length,
35968             ln = items.length,
35969             domLn = 0,
35970             i, j, cn, item;
35971
35972         // Calculate the number of DOM nodes in our target that are not our docked items
35973         for (i = 0; i < cnsLn; i++) {
35974             cn = Ext.get(cns[i]);
35975             for (j = 0; j < ln; j++) {
35976                 item = items[j];
35977                 if (item.rendered && (cn.id == item.el.id || cn.contains(item.el.id))) {
35978                     break;
35979                 }
35980             }
35981
35982             if (j === ln) {
35983                 domLn++;
35984             }
35985         }
35986
35987         // Now we go through our docked items and render/move them
35988         for (i = 0, j = 0; i < ln; i++, j++) {
35989             item = items[i];
35990
35991             // If we're now at the right/bottom docked item, we jump ahead in our
35992             // DOM position, just past the existing DOM nodes.
35993             //
35994             // TODO: This is affected if users provide custom weight values to their
35995             // docked items, which puts it out of (t,l,r,b) order. Avoiding a second
35996             // sort operation here, for now, in the name of performance. getDockedItems()
35997             // needs the sort operation not just for this layout-time rendering, but
35998             // also for getRefItems() to return a logical ordering (FocusManager, CQ, et al).
35999             if (i === j && (item.dock === 'right' || item.dock === 'bottom')) {
36000                 j += domLn;
36001             }
36002
36003             // Same logic as Layout.renderItems()
36004             if (item && !item.rendered) {
36005                 this.renderItem(item, target, j);
36006             }
36007             else if (!this.isValidParent(item, target, j)) {
36008                 this.moveItem(item, target, j);
36009             }
36010         }
36011     },
36012
36013     /**
36014      * @protected
36015      * This function will be called by the dockItems method. Since the body is positioned absolute,
36016      * we need to give it dimensions and a position so that it is in the middle surrounded by
36017      * docked items
36018      * @param {Object} box An object containing new x, y, width and height values for the
36019      * Panel's body
36020      */
36021     setBodyBox : function(box) {
36022         var me = this,
36023             owner = me.owner,
36024             body = owner.body,
36025             info = me.info,
36026             bodyMargin = info.bodyMargin,
36027             padding = info.padding,
36028             border = info.border,
36029             frameSize = me.frameSize;
36030         
36031         // Panel collapse effectively hides the Panel's body, so this is a no-op.
36032         if (owner.collapsed) {
36033             return;
36034         }
36035         
36036         if (Ext.isNumber(box.width)) {
36037             box.width -= bodyMargin.left + bodyMargin.right;
36038         }
36039         
36040         if (Ext.isNumber(box.height)) {
36041             box.height -= bodyMargin.top + bodyMargin.bottom;
36042         }
36043         
36044         me.setElementSize(body, box.width, box.height);
36045         if (Ext.isNumber(box.x)) {
36046             body.setLeft(box.x - padding.left - frameSize.left);
36047         }
36048         if (Ext.isNumber(box.y)) {
36049             body.setTop(box.y - padding.top - frameSize.top);
36050         }
36051     },
36052
36053     /**
36054      * @protected
36055      * We are overriding the Ext.layout.Layout configureItem method to also add a class that
36056      * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix.
36057      * An example of a class added to a dock: right item is x-docked-right
36058      * @param {Ext.Component} item The item we are configuring
36059      */
36060     configureItem : function(item, pos) {
36061         this.callParent(arguments);
36062         if (item.dock == 'top' || item.dock == 'bottom') {
36063             item.layoutManagedWidth = 1;
36064             item.layoutManagedHeight = 2;
36065         } else {
36066             item.layoutManagedWidth = 2;
36067             item.layoutManagedHeight = 1;
36068         }
36069         
36070         item.addCls(Ext.baseCSSPrefix + 'docked');
36071         item.addClsWithUI('docked-' + item.dock);
36072     },
36073
36074     afterRemove : function(item) {
36075         this.callParent(arguments);
36076         if (this.itemCls) {
36077             item.el.removeCls(this.itemCls + '-' + item.dock);
36078         }
36079         var dom = item.el.dom;
36080
36081         if (!item.destroying && dom) {
36082             dom.parentNode.removeChild(dom);
36083         }
36084         this.childrenChanged = true;
36085     }
36086 });
36087 /**
36088  * @class Ext.util.Memento
36089  * This class manages a set of captured properties from an object. These captured properties
36090  * can later be restored to an object.
36091  */
36092 Ext.define('Ext.util.Memento', function () {
36093
36094     function captureOne (src, target, prop) {
36095         src[prop] = target[prop];
36096     }
36097
36098     function removeOne (src, target, prop) {
36099         delete src[prop];
36100     }
36101
36102     function restoreOne (src, target, prop) {
36103         var value = src[prop];
36104         if (value || src.hasOwnProperty(prop)) {
36105             restoreValue(target, prop, value);
36106         }
36107     }
36108
36109     function restoreValue (target, prop, value) {
36110         if (Ext.isDefined(value)) {
36111             target[prop] = value;
36112         } else {
36113             delete target[prop];
36114         }
36115     }
36116
36117     function doMany (doOne, src, target, props) {
36118         if (src) {
36119             if (Ext.isArray(props)) {
36120                 Ext.each(props, function (prop) {
36121                     doOne(src, target, prop);
36122                 });
36123             } else {
36124                 doOne(src, target, props);
36125             }
36126         }
36127     }
36128
36129     return {
36130         /**
36131          * @property data
36132          * The collection of captured properties.
36133          * @private
36134          */
36135         data: null,
36136
36137         /**
36138          * @property target
36139          * The default target object for capture/restore (passed to the constructor).
36140          */
36141         target: null,
36142
36143         /**
36144          * Creates a new memento and optionally captures properties from the target object.
36145          * @param {Object} target The target from which to capture properties. If specified in the
36146          * constructor, this target becomes the default target for all other operations.
36147          * @param {String/String[]} props The property or array of properties to capture.
36148          */
36149         constructor: function (target, props) {
36150             if (target) {
36151                 this.target = target;
36152                 if (props) {
36153                     this.capture(props);
36154                 }
36155             }
36156         },
36157
36158         /**
36159          * Captures the specified properties from the target object in this memento.
36160          * @param {String/String[]} props The property or array of properties to capture.
36161          * @param {Object} target The object from which to capture properties.
36162          */
36163         capture: function (props, target) {
36164             doMany(captureOne, this.data || (this.data = {}), target || this.target, props);
36165         },
36166
36167         /**
36168          * Removes the specified properties from this memento. These properties will not be
36169          * restored later without re-capturing their values.
36170          * @param {String/String[]} props The property or array of properties to remove.
36171          */
36172         remove: function (props) {
36173             doMany(removeOne, this.data, null, props);
36174         },
36175
36176         /**
36177          * Restores the specified properties from this memento to the target object.
36178          * @param {String/String[]} props The property or array of properties to restore.
36179          * @param {Boolean} clear True to remove the restored properties from this memento or
36180          * false to keep them (default is true).
36181          * @param {Object} target The object to which to restore properties.
36182          */
36183         restore: function (props, clear, target) {
36184             doMany(restoreOne, this.data, target || this.target, props);
36185             if (clear !== false) {
36186                 this.remove(props);
36187             }
36188         },
36189
36190         /**
36191          * Restores all captured properties in this memento to the target object.
36192          * @param {Boolean} clear True to remove the restored properties from this memento or
36193          * false to keep them (default is true).
36194          * @param {Object} target The object to which to restore properties.
36195          */
36196         restoreAll: function (clear, target) {
36197             var me = this,
36198                 t = target || this.target;
36199
36200             Ext.Object.each(me.data, function (prop, value) {
36201                 restoreValue(t, prop, value);
36202             });
36203
36204             if (clear !== false) {
36205                 delete me.data;
36206             }
36207         }
36208     };
36209 }());
36210
36211 /**
36212  * @class Ext.app.EventBus
36213  * @private
36214  */
36215 Ext.define('Ext.app.EventBus', {
36216     requires: [
36217         'Ext.util.Event'
36218     ],
36219     mixins: {
36220         observable: 'Ext.util.Observable'
36221     },
36222
36223     constructor: function() {
36224         this.mixins.observable.constructor.call(this);
36225
36226         this.bus = {};
36227
36228         var me = this;
36229         Ext.override(Ext.Component, {
36230             fireEvent: function(ev) {
36231                 if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false) {
36232                     return me.dispatch.call(me, ev, this, arguments);
36233                 }
36234                 return false;
36235             }
36236         });
36237     },
36238
36239     dispatch: function(ev, target, args) {
36240         var bus = this.bus,
36241             selectors = bus[ev],
36242             selector, controllers, id, events, event, i, ln;
36243
36244         if (selectors) {
36245             // Loop over all the selectors that are bound to this event
36246             for (selector in selectors) {
36247                 // Check if the target matches the selector
36248                 if (target.is(selector)) {
36249                     // Loop over all the controllers that are bound to this selector
36250                     controllers = selectors[selector];
36251                     for (id in controllers) {
36252                         // Loop over all the events that are bound to this selector on this controller
36253                         events = controllers[id];
36254                         for (i = 0, ln = events.length; i < ln; i++) {
36255                             event = events[i];
36256                             // Fire the event!
36257                             if (event.fire.apply(event, Array.prototype.slice.call(args, 1)) === false) {
36258                                 return false;
36259                             };
36260                         }
36261                     }
36262                 }
36263             }
36264         }
36265     },
36266
36267     control: function(selectors, listeners, controller) {
36268         var bus = this.bus,
36269             selector, fn;
36270
36271         if (Ext.isString(selectors)) {
36272             selector = selectors;
36273             selectors = {};
36274             selectors[selector] = listeners;
36275             this.control(selectors, null, controller);
36276             return;
36277         }
36278
36279         Ext.Object.each(selectors, function(selector, listeners) {
36280             Ext.Object.each(listeners, function(ev, listener) {
36281                 var options = {},
36282                     scope = controller,
36283                     event = Ext.create('Ext.util.Event', controller, ev);
36284
36285                 // Normalize the listener
36286                 if (Ext.isObject(listener)) {
36287                     options = listener;
36288                     listener = options.fn;
36289                     scope = options.scope || controller;
36290                     delete options.fn;
36291                     delete options.scope;
36292                 }
36293
36294                 event.addListener(listener, scope, options);
36295
36296                 // Create the bus tree if it is not there yet
36297                 bus[ev] = bus[ev] || {};
36298                 bus[ev][selector] = bus[ev][selector] || {};
36299                 bus[ev][selector][controller.id] = bus[ev][selector][controller.id] || [];
36300
36301                 // Push our listener in our bus
36302                 bus[ev][selector][controller.id].push(event);
36303             });
36304         });
36305     }
36306 });
36307 /**
36308  * @class Ext.data.Types
36309  * <p>This is a static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
36310  * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
36311  * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
36312  * of this class.</p>
36313  * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
36314  * each type definition must contain three properties:</p>
36315  * <div class="mdetail-params"><ul>
36316  * <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
36317  * to be stored in the Field. The function is passed the collowing parameters:
36318  * <div class="mdetail-params"><ul>
36319  * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
36320  * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
36321  * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
36322  * Depending on the Reader type, this could be an Array ({@link Ext.data.reader.Array ArrayReader}), an object
36323  * ({@link Ext.data.reader.Json JsonReader}), or an XML element.</div></li>
36324  * </ul></div></div></li>
36325  * <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>
36326  * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
36327  * </ul></div>
36328  * <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
36329  * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
36330  *<pre><code>
36331 // Add a new Field data type which stores a VELatLong object in the Record.
36332 Ext.data.Types.VELATLONG = {
36333     convert: function(v, data) {
36334         return new VELatLong(data.lat, data.long);
36335     },
36336     sortType: function(v) {
36337         return v.Latitude;  // When sorting, order by latitude
36338     },
36339     type: 'VELatLong'
36340 };
36341 </code></pre>
36342  * <p>Then, when declaring a Model, use: <pre><code>
36343 var types = Ext.data.Types; // allow shorthand type access
36344 Ext.define('Unit',
36345     extend: 'Ext.data.Model',
36346     fields: [
36347         { name: 'unitName', mapping: 'UnitName' },
36348         { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
36349         { name: 'latitude', mapping: 'lat', type: types.FLOAT },
36350         { name: 'longitude', mapping: 'long', type: types.FLOAT },
36351         { name: 'position', type: types.VELATLONG }
36352     ]
36353 });
36354 </code></pre>
36355  * @singleton
36356  */
36357 Ext.define('Ext.data.Types', {
36358     singleton: true,
36359     requires: ['Ext.data.SortTypes']
36360 }, function() {
36361     var st = Ext.data.SortTypes;
36362
36363     Ext.apply(Ext.data.Types, {
36364         /**
36365          * @property {RegExp} stripRe
36366          * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
36367          * This should be overridden for localization.
36368          */
36369         stripRe: /[\$,%]/g,
36370
36371         /**
36372          * @property {Object} AUTO
36373          * This data type means that no conversion is applied to the raw data before it is placed into a Record.
36374          */
36375         AUTO: {
36376             convert: function(v) {
36377                 return v;
36378             },
36379             sortType: st.none,
36380             type: 'auto'
36381         },
36382
36383         /**
36384          * @property {Object} STRING
36385          * This data type means that the raw data is converted into a String before it is placed into a Record.
36386          */
36387         STRING: {
36388             convert: function(v) {
36389                 var defaultValue = this.useNull ? null : '';
36390                 return (v === undefined || v === null) ? defaultValue : String(v);
36391             },
36392             sortType: st.asUCString,
36393             type: 'string'
36394         },
36395
36396         /**
36397          * @property {Object} INT
36398          * This data type means that the raw data is converted into an integer before it is placed into a Record.
36399          * <p>The synonym <code>INTEGER</code> is equivalent.</p>
36400          */
36401         INT: {
36402             convert: function(v) {
36403                 return v !== undefined && v !== null && v !== '' ?
36404                     parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
36405             },
36406             sortType: st.none,
36407             type: 'int'
36408         },
36409
36410         /**
36411          * @property {Object} FLOAT
36412          * This data type means that the raw data is converted into a number before it is placed into a Record.
36413          * <p>The synonym <code>NUMBER</code> is equivalent.</p>
36414          */
36415         FLOAT: {
36416             convert: function(v) {
36417                 return v !== undefined && v !== null && v !== '' ?
36418                     parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
36419             },
36420             sortType: st.none,
36421             type: 'float'
36422         },
36423
36424         /**
36425          * @property {Object} BOOL
36426          * <p>This data type means that the raw data is converted into a boolean before it is placed into
36427          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
36428          * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
36429          */
36430         BOOL: {
36431             convert: function(v) {
36432                 if (this.useNull && (v === undefined || v === null || v === '')) {
36433                     return null;
36434                 }
36435                 return v === true || v === 'true' || v == 1;
36436             },
36437             sortType: st.none,
36438             type: 'bool'
36439         },
36440
36441         /**
36442          * @property {Object} DATE
36443          * This data type means that the raw data is converted into a Date before it is placed into a Record.
36444          * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
36445          * being applied.
36446          */
36447         DATE: {
36448             convert: function(v) {
36449                 var df = this.dateFormat,
36450                     parsed;
36451
36452                 if (!v) {
36453                     return null;
36454                 }
36455                 if (Ext.isDate(v)) {
36456                     return v;
36457                 }
36458                 if (df) {
36459                     if (df == 'timestamp') {
36460                         return new Date(v*1000);
36461                     }
36462                     if (df == 'time') {
36463                         return new Date(parseInt(v, 10));
36464                     }
36465                     return Ext.Date.parse(v, df);
36466                 }
36467
36468                 parsed = Date.parse(v);
36469                 return parsed ? new Date(parsed) : null;
36470             },
36471             sortType: st.asDate,
36472             type: 'date'
36473         }
36474     });
36475
36476     Ext.apply(Ext.data.Types, {
36477         /**
36478          * @property {Object} BOOLEAN
36479          * <p>This data type means that the raw data is converted into a boolean before it is placed into
36480          * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
36481          * <p>The synonym <code>BOOL</code> is equivalent.</p>
36482          */
36483         BOOLEAN: this.BOOL,
36484
36485         /**
36486          * @property {Object} INTEGER
36487          * This data type means that the raw data is converted into an integer before it is placed into a Record.
36488          * <p>The synonym <code>INT</code> is equivalent.</p>
36489          */
36490         INTEGER: this.INT,
36491
36492         /**
36493          * @property {Object} NUMBER
36494          * This data type means that the raw data is converted into a number before it is placed into a Record.
36495          * <p>The synonym <code>FLOAT</code> is equivalent.</p>
36496          */
36497         NUMBER: this.FLOAT
36498     });
36499 });
36500
36501 /**
36502  * @author Ed Spencer
36503  *
36504  * Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class that
36505  * extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a {@link
36506  * Ext.data.Model Model}. For example, we might set up a model like this:
36507  *
36508  *     Ext.define('User', {
36509  *         extend: 'Ext.data.Model',
36510  *         fields: [
36511  *             'name', 'email',
36512  *             {name: 'age', type: 'int'},
36513  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'}
36514  *         ]
36515  *     });
36516  *
36517  * Four fields will have been created for the User Model - name, email, age and gender. Note that we specified a couple
36518  * of different formats here; if we only pass in the string name of the field (as with name and email), the field is set
36519  * up with the 'auto' type. It's as if we'd done this instead:
36520  *
36521  *     Ext.define('User', {
36522  *         extend: 'Ext.data.Model',
36523  *         fields: [
36524  *             {name: 'name', type: 'auto'},
36525  *             {name: 'email', type: 'auto'},
36526  *             {name: 'age', type: 'int'},
36527  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'}
36528  *         ]
36529  *     });
36530  *
36531  * # Types and conversion
36532  *
36533  * The {@link #type} is important - it's used to automatically convert data passed to the field into the correct format.
36534  * In our example above, the name and email fields used the 'auto' type and will just accept anything that is passed
36535  * into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.
36536  *
36537  * Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can do
36538  * this using a {@link #convert} function. Here, we're going to create a new field based on another:
36539  *
36540  *     Ext.define('User', {
36541  *         extend: 'Ext.data.Model',
36542  *         fields: [
36543  *             'name', 'email',
36544  *             {name: 'age', type: 'int'},
36545  *             {name: 'gender', type: 'string', defaultValue: 'Unknown'},
36546  *
36547  *             {
36548  *                 name: 'firstName',
36549  *                 convert: function(value, record) {
36550  *                     var fullName  = record.get('name'),
36551  *                         splits    = fullName.split(" "),
36552  *                         firstName = splits[0];
36553  *
36554  *                     return firstName;
36555  *                 }
36556  *             }
36557  *         ]
36558  *     });
36559  *
36560  * Now when we create a new User, the firstName is populated automatically based on the name:
36561  *
36562  *     var ed = Ext.create('User', {name: 'Ed Spencer'});
36563  *
36564  *     console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
36565  *
36566  * In fact, if we log out all of the data inside ed, we'll see this:
36567  *
36568  *     console.log(ed.data);
36569  *
36570  *     //outputs this:
36571  *     {
36572  *         age: 0,
36573  *         email: "",
36574  *         firstName: "Ed",
36575  *         gender: "Unknown",
36576  *         name: "Ed Spencer"
36577  *     }
36578  *
36579  * The age field has been given a default of zero because we made it an int type. As an auto field, email has defaulted
36580  * to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown' so we see
36581  * that now. Let's correct that and satisfy ourselves that the types work as we expect:
36582  *
36583  *     ed.set('gender', 'Male');
36584  *     ed.get('gender'); //returns 'Male'
36585  *
36586  *     ed.set('age', 25.4);
36587  *     ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
36588  */
36589 Ext.define('Ext.data.Field', {
36590     requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
36591     alias: 'data.field',
36592     
36593     constructor : function(config) {
36594         if (Ext.isString(config)) {
36595             config = {name: config};
36596         }
36597         Ext.apply(this, config);
36598         
36599         var types = Ext.data.Types,
36600             st = this.sortType,
36601             t;
36602
36603         if (this.type) {
36604             if (Ext.isString(this.type)) {
36605                 this.type = types[this.type.toUpperCase()] || types.AUTO;
36606             }
36607         } else {
36608             this.type = types.AUTO;
36609         }
36610
36611         // named sortTypes are supported, here we look them up
36612         if (Ext.isString(st)) {
36613             this.sortType = Ext.data.SortTypes[st];
36614         } else if(Ext.isEmpty(st)) {
36615             this.sortType = this.type.sortType;
36616         }
36617
36618         if (!this.convert) {
36619             this.convert = this.type.convert;
36620         }
36621     },
36622     
36623     /**
36624      * @cfg {String} name
36625      *
36626      * The name by which the field is referenced within the Model. This is referenced by, for example, the `dataIndex`
36627      * property in column definition objects passed to {@link Ext.grid.property.HeaderContainer}.
36628      *
36629      * Note: In the simplest case, if no properties other than `name` are required, a field definition may consist of
36630      * just a String for the field name.
36631      */
36632     
36633     /**
36634      * @cfg {String/Object} type
36635      *
36636      * The data type for automatic conversion from received data to the *stored* value if
36637      * `{@link Ext.data.Field#convert convert}` has not been specified. This may be specified as a string value.
36638      * Possible values are
36639      *
36640      * - auto (Default, implies no conversion)
36641      * - string
36642      * - int
36643      * - float
36644      * - boolean
36645      * - date
36646      *
36647      * This may also be specified by referencing a member of the {@link Ext.data.Types} class.
36648      *
36649      * Developers may create their own application-specific data types by defining new members of the {@link
36650      * Ext.data.Types} class.
36651      */
36652     
36653     /**
36654      * @cfg {Function} convert
36655      *
36656      * A function which converts the value provided by the Reader into an object that will be stored in the Model.
36657      * It is passed the following parameters:
36658      *
36659      * - **v** : Mixed
36660      *
36661      *   The data value as read by the Reader, if undefined will use the configured `{@link Ext.data.Field#defaultValue
36662      *   defaultValue}`.
36663      *
36664      * - **rec** : Ext.data.Model
36665      *
36666      *   The data object containing the Model as read so far by the Reader. Note that the Model may not be fully populated
36667      *   at this point as the fields are read in the order that they are defined in your
36668      *   {@link Ext.data.Model#fields fields} array.
36669      *
36670      * Example of convert functions:
36671      *
36672      *     function fullName(v, record){
36673      *         return record.name.last + ', ' + record.name.first;
36674      *     }
36675      *
36676      *     function location(v, record){
36677      *         return !record.city ? '' : (record.city + ', ' + record.state);
36678      *     }
36679      *
36680      *     Ext.define('Dude', {
36681      *         extend: 'Ext.data.Model',
36682      *         fields: [
36683      *             {name: 'fullname',  convert: fullName},
36684      *             {name: 'firstname', mapping: 'name.first'},
36685      *             {name: 'lastname',  mapping: 'name.last'},
36686      *             {name: 'city', defaultValue: 'homeless'},
36687      *             'state',
36688      *             {name: 'location',  convert: location}
36689      *         ]
36690      *     });
36691      *
36692      *     // create the data store
36693      *     var store = Ext.create('Ext.data.Store', {
36694      *         reader: {
36695      *             type: 'json',
36696      *             model: 'Dude',
36697      *             idProperty: 'key',
36698      *             root: 'daRoot',
36699      *             totalProperty: 'total'
36700      *         }
36701      *     });
36702      *
36703      *     var myData = [
36704      *         { key: 1,
36705      *           name: { first: 'Fat',    last:  'Albert' }
36706      *           // notice no city, state provided in data object
36707      *         },
36708      *         { key: 2,
36709      *           name: { first: 'Barney', last:  'Rubble' },
36710      *           city: 'Bedrock', state: 'Stoneridge'
36711      *         },
36712      *         { key: 3,
36713      *           name: { first: 'Cliff',  last:  'Claven' },
36714      *           city: 'Boston',  state: 'MA'
36715      *         }
36716      *     ];
36717      */
36718
36719     /**
36720      * @cfg {String} dateFormat
36721      *
36722      * Used when converting received data into a Date when the {@link #type} is specified as `"date"`.
36723      *
36724      * A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the value provided by
36725      * the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a javascript millisecond
36726      * timestamp. See {@link Ext.Date}.
36727      */
36728     dateFormat: null,
36729     
36730     /**
36731      * @cfg {Boolean} useNull
36732      *
36733      * Use when converting received data into a Number type (either int or float). If the value cannot be
36734      * parsed, null will be used if useNull is true, otherwise the value will be 0. Defaults to false.
36735      */
36736     useNull: false,
36737     
36738     /**
36739      * @cfg {Object} defaultValue
36740      *
36741      * The default value used **when a Model is being created by a {@link Ext.data.reader.Reader Reader}**
36742      * when the item referenced by the `{@link Ext.data.Field#mapping mapping}` does not exist in the data object
36743      * (i.e. undefined). Defaults to "".
36744      */
36745     defaultValue: "",
36746
36747     /**
36748      * @cfg {String/Number} mapping
36749      *
36750      * (Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation that is creating the
36751      * {@link Ext.data.Model Model} to extract the Field value from the data object. If the path expression is the same
36752      * as the field name, the mapping may be omitted.
36753      *
36754      * The form of the mapping expression depends on the Reader being used.
36755      *
36756      * - {@link Ext.data.reader.Json}
36757      *
36758      *   The mapping is a string containing the javascript expression to reference the data from an element of the data
36759      *   item's {@link Ext.data.reader.Json#root root} Array. Defaults to the field name.
36760      *
36761      * - {@link Ext.data.reader.Xml}
36762      *
36763      *   The mapping is an {@link Ext.DomQuery} path to the data item relative to the DOM element that represents the
36764      *   {@link Ext.data.reader.Xml#record record}. Defaults to the field name.
36765      *
36766      * - {@link Ext.data.reader.Array}
36767      *
36768      *   The mapping is a number indicating the Array index of the field's value. Defaults to the field specification's
36769      *   Array position.
36770      *
36771      * If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
36772      * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
36773      * return the desired data.
36774      */
36775     mapping: null,
36776
36777     /**
36778      * @cfg {Function} sortType
36779      *
36780      * A function which converts a Field's value to a comparable value in order to ensure correct sort ordering.
36781      * Predefined functions are provided in {@link Ext.data.SortTypes}. A custom sort example:
36782      *
36783      *     // current sort     after sort we want
36784      *     // +-+------+          +-+------+
36785      *     // |1|First |          |1|First |
36786      *     // |2|Last  |          |3|Second|
36787      *     // |3|Second|          |2|Last  |
36788      *     // +-+------+          +-+------+
36789      *
36790      *     sortType: function(value) {
36791      *        switch (value.toLowerCase()) // native toLowerCase():
36792      *        {
36793      *           case 'first': return 1;
36794      *           case 'second': return 2;
36795      *           default: return 3;
36796      *        }
36797      *     }
36798      */
36799     sortType : null,
36800
36801     /**
36802      * @cfg {String} sortDir
36803      *
36804      * Initial direction to sort (`"ASC"` or `"DESC"`). Defaults to `"ASC"`.
36805      */
36806     sortDir : "ASC",
36807
36808     /**
36809      * @cfg {Boolean} allowBlank
36810      * @private
36811      *
36812      * Used for validating a {@link Ext.data.Model model}. Defaults to true. An empty value here will cause
36813      * {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid} to evaluate to false.
36814      */
36815     allowBlank : true,
36816
36817     /**
36818      * @cfg {Boolean} persist
36819      *
36820      * False to exclude this field from the {@link Ext.data.Model#modified} fields in a model. This will also exclude
36821      * the field from being written using a {@link Ext.data.writer.Writer}. This option is useful when model fields are
36822      * used to keep state on the client but do not need to be persisted to the server. Defaults to true.
36823      */
36824     persist: true
36825 });
36826
36827 /**
36828  * @class Ext.util.AbstractMixedCollection
36829  * @private
36830  */
36831 Ext.define('Ext.util.AbstractMixedCollection', {
36832     requires: ['Ext.util.Filter'],
36833
36834     mixins: {
36835         observable: 'Ext.util.Observable'
36836     },
36837
36838     constructor: function(allowFunctions, keyFn) {
36839         var me = this;
36840
36841         me.items = [];
36842         me.map = {};
36843         me.keys = [];
36844         me.length = 0;
36845
36846         me.addEvents(
36847             /**
36848              * @event clear
36849              * Fires when the collection is cleared.
36850              */
36851             'clear',
36852
36853             /**
36854              * @event add
36855              * Fires when an item is added to the collection.
36856              * @param {Number} index The index at which the item was added.
36857              * @param {Object} o The item added.
36858              * @param {String} key The key associated with the added item.
36859              */
36860             'add',
36861
36862             /**
36863              * @event replace
36864              * Fires when an item is replaced in the collection.
36865              * @param {String} key he key associated with the new added.
36866              * @param {Object} old The item being replaced.
36867              * @param {Object} new The new item.
36868              */
36869             'replace',
36870
36871             /**
36872              * @event remove
36873              * Fires when an item is removed from the collection.
36874              * @param {Object} o The item being removed.
36875              * @param {String} key (optional) The key associated with the removed item.
36876              */
36877             'remove'
36878         );
36879
36880         me.allowFunctions = allowFunctions === true;
36881
36882         if (keyFn) {
36883             me.getKey = keyFn;
36884         }
36885
36886         me.mixins.observable.constructor.call(me);
36887     },
36888
36889     /**
36890      * @cfg {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
36891      * function should add function references to the collection. Defaults to
36892      * <tt>false</tt>.
36893      */
36894     allowFunctions : false,
36895
36896     /**
36897      * Adds an item to the collection. Fires the {@link #add} event when complete.
36898      * @param {String} key <p>The key to associate with the item, or the new item.</p>
36899      * <p>If a {@link #getKey} implementation was specified for this MixedCollection,
36900      * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
36901      * the MixedCollection will be able to <i>derive</i> the key for the new item.
36902      * In this case just pass the new item in this parameter.</p>
36903      * @param {Object} o The item to add.
36904      * @return {Object} The item added.
36905      */
36906     add : function(key, obj){
36907         var me = this,
36908             myObj = obj,
36909             myKey = key,
36910             old;
36911
36912         if (arguments.length == 1) {
36913             myObj = myKey;
36914             myKey = me.getKey(myObj);
36915         }
36916         if (typeof myKey != 'undefined' && myKey !== null) {
36917             old = me.map[myKey];
36918             if (typeof old != 'undefined') {
36919                 return me.replace(myKey, myObj);
36920             }
36921             me.map[myKey] = myObj;
36922         }
36923         me.length++;
36924         me.items.push(myObj);
36925         me.keys.push(myKey);
36926         me.fireEvent('add', me.length - 1, myObj, myKey);
36927         return myObj;
36928     },
36929
36930     /**
36931       * MixedCollection has a generic way to fetch keys if you implement getKey.  The default implementation
36932       * simply returns <b><code>item.id</code></b> but you can provide your own implementation
36933       * to return a different value as in the following examples:<pre><code>
36934 // normal way
36935 var mc = new Ext.util.MixedCollection();
36936 mc.add(someEl.dom.id, someEl);
36937 mc.add(otherEl.dom.id, otherEl);
36938 //and so on
36939
36940 // using getKey
36941 var mc = new Ext.util.MixedCollection();
36942 mc.getKey = function(el){
36943    return el.dom.id;
36944 };
36945 mc.add(someEl);
36946 mc.add(otherEl);
36947
36948 // or via the constructor
36949 var mc = new Ext.util.MixedCollection(false, function(el){
36950    return el.dom.id;
36951 });
36952 mc.add(someEl);
36953 mc.add(otherEl);
36954      * </code></pre>
36955      * @param {Object} item The item for which to find the key.
36956      * @return {Object} The key for the passed item.
36957      */
36958     getKey : function(o){
36959          return o.id;
36960     },
36961
36962     /**
36963      * Replaces an item in the collection. Fires the {@link #replace} event when complete.
36964      * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
36965      * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
36966      * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
36967      * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
36968      * with one having the same key value, then just pass the replacement item in this parameter.</p>
36969      * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
36970      * with that key.
36971      * @return {Object}  The new item.
36972      */
36973     replace : function(key, o){
36974         var me = this,
36975             old,
36976             index;
36977
36978         if (arguments.length == 1) {
36979             o = arguments[0];
36980             key = me.getKey(o);
36981         }
36982         old = me.map[key];
36983         if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
36984              return me.add(key, o);
36985         }
36986         index = me.indexOfKey(key);
36987         me.items[index] = o;
36988         me.map[key] = o;
36989         me.fireEvent('replace', key, old, o);
36990         return o;
36991     },
36992
36993     /**
36994      * Adds all elements of an Array or an Object to the collection.
36995      * @param {Object/Array} objs An Object containing properties which will be added
36996      * to the collection, or an Array of values, each of which are added to the collection.
36997      * Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
36998      * has been set to <tt>true</tt>.
36999      */
37000     addAll : function(objs){
37001         var me = this,
37002             i = 0,
37003             args,
37004             len,
37005             key;
37006
37007         if (arguments.length > 1 || Ext.isArray(objs)) {
37008             args = arguments.length > 1 ? arguments : objs;
37009             for (len = args.length; i < len; i++) {
37010                 me.add(args[i]);
37011             }
37012         } else {
37013             for (key in objs) {
37014                 if (objs.hasOwnProperty(key)) {
37015                     if (me.allowFunctions || typeof objs[key] != 'function') {
37016                         me.add(key, objs[key]);
37017                     }
37018                 }
37019             }
37020         }
37021     },
37022
37023     /**
37024      * Executes the specified function once for every item in the collection, passing the following arguments:
37025      * <div class="mdetail-params"><ul>
37026      * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
37027      * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
37028      * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
37029      * </ul></div>
37030      * The function should return a boolean value. Returning false from the function will stop the iteration.
37031      * @param {Function} fn The function to execute for each item.
37032      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current item in the iteration.
37033      */
37034     each : function(fn, scope){
37035         var items = [].concat(this.items), // each safe for removal
37036             i = 0,
37037             len = items.length,
37038             item;
37039
37040         for (; i < len; i++) {
37041             item = items[i];
37042             if (fn.call(scope || item, item, i, len) === false) {
37043                 break;
37044             }
37045         }
37046     },
37047
37048     /**
37049      * Executes the specified function once for every key in the collection, passing each
37050      * key, and its associated item as the first two parameters.
37051      * @param {Function} fn The function to execute for each item.
37052      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
37053      */
37054     eachKey : function(fn, scope){
37055         var keys = this.keys,
37056             items = this.items,
37057             i = 0,
37058             len = keys.length;
37059
37060         for (; i < len; i++) {
37061             fn.call(scope || window, keys[i], items[i], i, len);
37062         }
37063     },
37064
37065     /**
37066      * Returns the first item in the collection which elicits a true return value from the
37067      * passed selection function.
37068      * @param {Function} fn The selection function to execute for each item.
37069      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
37070      * @return {Object} The first item in the collection which returned true from the selection function, or null if none was found
37071      */
37072     findBy : function(fn, scope) {
37073         var keys = this.keys,
37074             items = this.items,
37075             i = 0,
37076             len = items.length;
37077
37078         for (; i < len; i++) {
37079             if (fn.call(scope || window, items[i], keys[i])) {
37080                 return items[i];
37081             }
37082         }
37083         return null;
37084     },
37085
37086     find : function() {
37087         if (Ext.isDefined(Ext.global.console)) {
37088             Ext.global.console.warn('Ext.util.MixedCollection: find has been deprecated. Use findBy instead.');
37089         }
37090         return this.findBy.apply(this, arguments);
37091     },
37092
37093     /**
37094      * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
37095      * @param {Number} index The index to insert the item at.
37096      * @param {String} key The key to associate with the new item, or the item itself.
37097      * @param {Object} o (optional) If the second parameter was a key, the new item.
37098      * @return {Object} The item inserted.
37099      */
37100     insert : function(index, key, obj){
37101         var me = this,
37102             myKey = key,
37103             myObj = obj;
37104
37105         if (arguments.length == 2) {
37106             myObj = myKey;
37107             myKey = me.getKey(myObj);
37108         }
37109         if (me.containsKey(myKey)) {
37110             me.suspendEvents();
37111             me.removeAtKey(myKey);
37112             me.resumeEvents();
37113         }
37114         if (index >= me.length) {
37115             return me.add(myKey, myObj);
37116         }
37117         me.length++;
37118         Ext.Array.splice(me.items, index, 0, myObj);
37119         if (typeof myKey != 'undefined' && myKey !== null) {
37120             me.map[myKey] = myObj;
37121         }
37122         Ext.Array.splice(me.keys, index, 0, myKey);
37123         me.fireEvent('add', index, myObj, myKey);
37124         return myObj;
37125     },
37126
37127     /**
37128      * Remove an item from the collection.
37129      * @param {Object} o The item to remove.
37130      * @return {Object} The item removed or false if no item was removed.
37131      */
37132     remove : function(o){
37133         return this.removeAt(this.indexOf(o));
37134     },
37135
37136     /**
37137      * Remove all items in the passed array from the collection.
37138      * @param {Array} items An array of items to be removed.
37139      * @return {Ext.util.MixedCollection} this object
37140      */
37141     removeAll : function(items){
37142         Ext.each(items || [], function(item) {
37143             this.remove(item);
37144         }, this);
37145
37146         return this;
37147     },
37148
37149     /**
37150      * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
37151      * @param {Number} index The index within the collection of the item to remove.
37152      * @return {Object} The item removed or false if no item was removed.
37153      */
37154     removeAt : function(index){
37155         var me = this,
37156             o,
37157             key;
37158
37159         if (index < me.length && index >= 0) {
37160             me.length--;
37161             o = me.items[index];
37162             Ext.Array.erase(me.items, index, 1);
37163             key = me.keys[index];
37164             if (typeof key != 'undefined') {
37165                 delete me.map[key];
37166             }
37167             Ext.Array.erase(me.keys, index, 1);
37168             me.fireEvent('remove', o, key);
37169             return o;
37170         }
37171         return false;
37172     },
37173
37174     /**
37175      * Removed an item associated with the passed key fom the collection.
37176      * @param {String} key The key of the item to remove.
37177      * @return {Object} The item removed or false if no item was removed.
37178      */
37179     removeAtKey : function(key){
37180         return this.removeAt(this.indexOfKey(key));
37181     },
37182
37183     /**
37184      * Returns the number of items in the collection.
37185      * @return {Number} the number of items in the collection.
37186      */
37187     getCount : function(){
37188         return this.length;
37189     },
37190
37191     /**
37192      * Returns index within the collection of the passed Object.
37193      * @param {Object} o The item to find the index of.
37194      * @return {Number} index of the item. Returns -1 if not found.
37195      */
37196     indexOf : function(o){
37197         return Ext.Array.indexOf(this.items, o);
37198     },
37199
37200     /**
37201      * Returns index within the collection of the passed key.
37202      * @param {String} key The key to find the index of.
37203      * @return {Number} index of the key.
37204      */
37205     indexOfKey : function(key){
37206         return Ext.Array.indexOf(this.keys, key);
37207     },
37208
37209     /**
37210      * Returns the item associated with the passed key OR index.
37211      * Key has priority over index.  This is the equivalent
37212      * of calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
37213      * @param {String/Number} key The key or index of the item.
37214      * @return {Object} If the item is found, returns the item.  If the item was not found, returns <tt>undefined</tt>.
37215      * If an item was found, but is a Class, returns <tt>null</tt>.
37216      */
37217     get : function(key) {
37218         var me = this,
37219             mk = me.map[key],
37220             item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
37221         return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
37222     },
37223
37224     /**
37225      * Returns the item at the specified index.
37226      * @param {Number} index The index of the item.
37227      * @return {Object} The item at the specified index.
37228      */
37229     getAt : function(index) {
37230         return this.items[index];
37231     },
37232
37233     /**
37234      * Returns the item associated with the passed key.
37235      * @param {String/Number} key The key of the item.
37236      * @return {Object} The item associated with the passed key.
37237      */
37238     getByKey : function(key) {
37239         return this.map[key];
37240     },
37241
37242     /**
37243      * Returns true if the collection contains the passed Object as an item.
37244      * @param {Object} o  The Object to look for in the collection.
37245      * @return {Boolean} True if the collection contains the Object as an item.
37246      */
37247     contains : function(o){
37248         return Ext.Array.contains(this.items, o);
37249     },
37250
37251     /**
37252      * Returns true if the collection contains the passed Object as a key.
37253      * @param {String} key The key to look for in the collection.
37254      * @return {Boolean} True if the collection contains the Object as a key.
37255      */
37256     containsKey : function(key){
37257         return typeof this.map[key] != 'undefined';
37258     },
37259
37260     /**
37261      * Removes all items from the collection.  Fires the {@link #clear} event when complete.
37262      */
37263     clear : function(){
37264         var me = this;
37265
37266         me.length = 0;
37267         me.items = [];
37268         me.keys = [];
37269         me.map = {};
37270         me.fireEvent('clear');
37271     },
37272
37273     /**
37274      * Returns the first item in the collection.
37275      * @return {Object} the first item in the collection..
37276      */
37277     first : function() {
37278         return this.items[0];
37279     },
37280
37281     /**
37282      * Returns the last item in the collection.
37283      * @return {Object} the last item in the collection..
37284      */
37285     last : function() {
37286         return this.items[this.length - 1];
37287     },
37288
37289     /**
37290      * Collects all of the values of the given property and returns their sum
37291      * @param {String} property The property to sum by
37292      * @param {String} [root] 'root' property to extract the first argument from. This is used mainly when
37293      * summing fields in records, where the fields are all stored inside the 'data' object
37294      * @param {Number} [start=0] The record index to start at
37295      * @param {Number} [end=-1] The record index to end at
37296      * @return {Number} The total
37297      */
37298     sum: function(property, root, start, end) {
37299         var values = this.extractValues(property, root),
37300             length = values.length,
37301             sum    = 0,
37302             i;
37303
37304         start = start || 0;
37305         end   = (end || end === 0) ? end : length - 1;
37306
37307         for (i = start; i <= end; i++) {
37308             sum += values[i];
37309         }
37310
37311         return sum;
37312     },
37313
37314     /**
37315      * Collects unique values of a particular property in this MixedCollection
37316      * @param {String} property The property to collect on
37317      * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
37318      * summing fields in records, where the fields are all stored inside the 'data' object
37319      * @param {Boolean} allowBlank (optional) Pass true to allow null, undefined or empty string values
37320      * @return {Array} The unique values
37321      */
37322     collect: function(property, root, allowNull) {
37323         var values = this.extractValues(property, root),
37324             length = values.length,
37325             hits   = {},
37326             unique = [],
37327             value, strValue, i;
37328
37329         for (i = 0; i < length; i++) {
37330             value = values[i];
37331             strValue = String(value);
37332
37333             if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
37334                 hits[strValue] = true;
37335                 unique.push(value);
37336             }
37337         }
37338
37339         return unique;
37340     },
37341
37342     /**
37343      * @private
37344      * Extracts all of the given property values from the items in the MC. Mainly used as a supporting method for
37345      * functions like sum and collect.
37346      * @param {String} property The property to extract
37347      * @param {String} root (optional) 'root' property to extract the first argument from. This is used mainly when
37348      * extracting field data from Model instances, where the fields are stored inside the 'data' object
37349      * @return {Array} The extracted values
37350      */
37351     extractValues: function(property, root) {
37352         var values = this.items;
37353
37354         if (root) {
37355             values = Ext.Array.pluck(values, root);
37356         }
37357
37358         return Ext.Array.pluck(values, property);
37359     },
37360
37361     /**
37362      * Returns a range of items in this collection
37363      * @param {Number} startIndex (optional) The starting index. Defaults to 0.
37364      * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
37365      * @return {Array} An array of items
37366      */
37367     getRange : function(start, end){
37368         var me = this,
37369             items = me.items,
37370             range = [],
37371             i;
37372
37373         if (items.length < 1) {
37374             return range;
37375         }
37376
37377         start = start || 0;
37378         end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
37379         if (start <= end) {
37380             for (i = start; i <= end; i++) {
37381                 range[range.length] = items[i];
37382             }
37383         } else {
37384             for (i = start; i >= end; i--) {
37385                 range[range.length] = items[i];
37386             }
37387         }
37388         return range;
37389     },
37390
37391     /**
37392      * <p>Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
37393      * property/value pair with optional parameters for substring matching and case sensitivity. See
37394      * {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
37395      * MixedCollection can be easily filtered by property like this:</p>
37396 <pre><code>
37397 //create a simple store with a few people defined
37398 var people = new Ext.util.MixedCollection();
37399 people.addAll([
37400     {id: 1, age: 25, name: 'Ed'},
37401     {id: 2, age: 24, name: 'Tommy'},
37402     {id: 3, age: 24, name: 'Arne'},
37403     {id: 4, age: 26, name: 'Aaron'}
37404 ]);
37405
37406 //a new MixedCollection containing only the items where age == 24
37407 var middleAged = people.filter('age', 24);
37408 </code></pre>
37409      *
37410      *
37411      * @param {Ext.util.Filter[]/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
37412      * @param {String/RegExp} value Either string that the property values
37413      * should start with or a RegExp to test against the property
37414      * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning
37415      * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
37416      * @return {Ext.util.MixedCollection} The new filtered collection
37417      */
37418     filter : function(property, value, anyMatch, caseSensitive) {
37419         var filters = [],
37420             filterFn;
37421
37422         //support for the simple case of filtering by property/value
37423         if (Ext.isString(property)) {
37424             filters.push(Ext.create('Ext.util.Filter', {
37425                 property     : property,
37426                 value        : value,
37427                 anyMatch     : anyMatch,
37428                 caseSensitive: caseSensitive
37429             }));
37430         } else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
37431             filters = filters.concat(property);
37432         }
37433
37434         //at this point we have an array of zero or more Ext.util.Filter objects to filter with,
37435         //so here we construct a function that combines these filters by ANDing them together
37436         filterFn = function(record) {
37437             var isMatch = true,
37438                 length = filters.length,
37439                 i;
37440
37441             for (i = 0; i < length; i++) {
37442                 var filter = filters[i],
37443                     fn     = filter.filterFn,
37444                     scope  = filter.scope;
37445
37446                 isMatch = isMatch && fn.call(scope, record);
37447             }
37448
37449             return isMatch;
37450         };
37451
37452         return this.filterBy(filterFn);
37453     },
37454
37455     /**
37456      * Filter by a function. Returns a <i>new</i> collection that has been filtered.
37457      * The passed function will be called with each object in the collection.
37458      * If the function returns true, the value is included otherwise it is filtered.
37459      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
37460      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
37461      * @return {Ext.util.MixedCollection} The new filtered collection
37462      */
37463     filterBy : function(fn, scope) {
37464         var me = this,
37465             newMC  = new this.self(),
37466             keys   = me.keys,
37467             items  = me.items,
37468             length = items.length,
37469             i;
37470
37471         newMC.getKey = me.getKey;
37472
37473         for (i = 0; i < length; i++) {
37474             if (fn.call(scope || me, items[i], keys[i])) {
37475                 newMC.add(keys[i], items[i]);
37476             }
37477         }
37478
37479         return newMC;
37480     },
37481
37482     /**
37483      * Finds the index of the first matching object in this collection by a specific property/value.
37484      * @param {String} property The name of a property on your objects.
37485      * @param {String/RegExp} value A string that the property values
37486      * should start with or a RegExp to test against the property.
37487      * @param {Number} [start=0] The index to start searching at.
37488      * @param {Boolean} [anyMatch=false] True to match any part of the string, not just the beginning.
37489      * @param {Boolean} [caseSensitive=false] True for case sensitive comparison.
37490      * @return {Number} The matched index or -1
37491      */
37492     findIndex : function(property, value, start, anyMatch, caseSensitive){
37493         if(Ext.isEmpty(value, false)){
37494             return -1;
37495         }
37496         value = this.createValueMatcher(value, anyMatch, caseSensitive);
37497         return this.findIndexBy(function(o){
37498             return o && value.test(o[property]);
37499         }, null, start);
37500     },
37501
37502     /**
37503      * Find the index of the first matching object in this collection by a function.
37504      * If the function returns <i>true</i> it is considered a match.
37505      * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
37506      * @param {Object} [scope] The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
37507      * @param {Number} [start=0] The index to start searching at.
37508      * @return {Number} The matched index or -1
37509      */
37510     findIndexBy : function(fn, scope, start){
37511         var me = this,
37512             keys = me.keys,
37513             items = me.items,
37514             i = start || 0,
37515             len = items.length;
37516
37517         for (; i < len; i++) {
37518             if (fn.call(scope || me, items[i], keys[i])) {
37519                 return i;
37520             }
37521         }
37522         return -1;
37523     },
37524
37525     /**
37526      * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
37527      * and by Ext.data.Store#filter
37528      * @private
37529      * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
37530      * @param {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
37531      * @param {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
37532      * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
37533      */
37534     createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
37535         if (!value.exec) { // not a regex
37536             var er = Ext.String.escapeRegex;
37537             value = String(value);
37538
37539             if (anyMatch === true) {
37540                 value = er(value);
37541             } else {
37542                 value = '^' + er(value);
37543                 if (exactMatch === true) {
37544                     value += '$';
37545                 }
37546             }
37547             value = new RegExp(value, caseSensitive ? '' : 'i');
37548         }
37549         return value;
37550     },
37551
37552     /**
37553      * Creates a shallow copy of this collection
37554      * @return {Ext.util.MixedCollection}
37555      */
37556     clone : function() {
37557         var me = this,
37558             copy = new this.self(),
37559             keys = me.keys,
37560             items = me.items,
37561             i = 0,
37562             len = items.length;
37563
37564         for(; i < len; i++){
37565             copy.add(keys[i], items[i]);
37566         }
37567         copy.getKey = me.getKey;
37568         return copy;
37569     }
37570 });
37571
37572 /**
37573  * @docauthor Tommy Maintz <tommy@sencha.com>
37574  *
37575  * 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}.
37576  *
37577  * **NOTE**: This mixin is mainly for internal use and most users should not need to use it directly. It
37578  * is more likely you will want to use one of the component classes that import this mixin, such as
37579  * {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
37580  */
37581 Ext.define("Ext.util.Sortable", {
37582     /**
37583      * @property {Boolean} isSortable
37584      * Flag denoting that this object is sortable. Always true.
37585      */
37586     isSortable: true,
37587
37588     /**
37589      * @property {String} defaultSortDirection
37590      * The default sort direction to use if one is not specified.
37591      */
37592     defaultSortDirection: "ASC",
37593
37594     requires: [
37595         'Ext.util.Sorter'
37596     ],
37597
37598     /**
37599      * @property {String} sortRoot
37600      * The property in each item that contains the data to sort.
37601      */
37602
37603     /**
37604      * Performs initialization of this mixin. Component classes using this mixin should call this method during their
37605      * own initialization.
37606      */
37607     initSortable: function() {
37608         var me = this,
37609             sorters = me.sorters;
37610
37611         /**
37612          * @property {Ext.util.MixedCollection} sorters
37613          * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
37614          */
37615         me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
37616             return item.id || item.property;
37617         });
37618
37619         if (sorters) {
37620             me.sorters.addAll(me.decodeSorters(sorters));
37621         }
37622     },
37623
37624     /**
37625      * Sorts the data in the Store by one or more of its properties. Example usage:
37626      *
37627      *     //sort by a single field
37628      *     myStore.sort('myField', 'DESC');
37629      *
37630      *     //sorting by multiple fields
37631      *     myStore.sort([
37632      *         {
37633      *             property : 'age',
37634      *             direction: 'ASC'
37635      *         },
37636      *         {
37637      *             property : 'name',
37638      *             direction: 'DESC'
37639      *         }
37640      *     ]);
37641      *
37642      * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
37643      * the actual sorting to its internal {@link Ext.util.MixedCollection}.
37644      *
37645      * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
37646      *
37647      *     store.sort('myField');
37648      *     store.sort('myField');
37649      *
37650      * Is equivalent to this code, because Store handles the toggling automatically:
37651      *
37652      *     store.sort('myField', 'ASC');
37653      *     store.sort('myField', 'DESC');
37654      *
37655      * @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
37656      * {@link Ext.data.Model Model}, or an array of sorter configurations.
37657      * @param {String} direction The overall direction to sort the data by. Defaults to "ASC".
37658      * @return {Ext.util.Sorter[]}
37659      */
37660     sort: function(sorters, direction, where, doSort) {
37661         var me = this,
37662             sorter, sorterFn,
37663             newSorters;
37664
37665         if (Ext.isArray(sorters)) {
37666             doSort = where;
37667             where = direction;
37668             newSorters = sorters;
37669         }
37670         else if (Ext.isObject(sorters)) {
37671             doSort = where;
37672             where = direction;
37673             newSorters = [sorters];
37674         }
37675         else if (Ext.isString(sorters)) {
37676             sorter = me.sorters.get(sorters);
37677
37678             if (!sorter) {
37679                 sorter = {
37680                     property : sorters,
37681                     direction: direction
37682                 };
37683                 newSorters = [sorter];
37684             }
37685             else if (direction === undefined) {
37686                 sorter.toggle();
37687             }
37688             else {
37689                 sorter.setDirection(direction);
37690             }
37691         }
37692
37693         if (newSorters && newSorters.length) {
37694             newSorters = me.decodeSorters(newSorters);
37695             if (Ext.isString(where)) {
37696                 if (where === 'prepend') {
37697                     sorters = me.sorters.clone().items;
37698
37699                     me.sorters.clear();
37700                     me.sorters.addAll(newSorters);
37701                     me.sorters.addAll(sorters);
37702                 }
37703                 else {
37704                     me.sorters.addAll(newSorters);
37705                 }
37706             }
37707             else {
37708                 me.sorters.clear();
37709                 me.sorters.addAll(newSorters);
37710             }
37711         }
37712
37713         if (doSort !== false) {
37714             me.onBeforeSort(newSorters);
37715             
37716             sorters = me.sorters.items;
37717             if (sorters.length) {
37718                 //construct an amalgamated sorter function which combines all of the Sorters passed
37719                 sorterFn = function(r1, r2) {
37720                     var result = sorters[0].sort(r1, r2),
37721                         length = sorters.length,
37722                         i;
37723
37724                         //if we have more than one sorter, OR any additional sorter functions together
37725                         for (i = 1; i < length; i++) {
37726                             result = result || sorters[i].sort.call(this, r1, r2);
37727                         }
37728
37729                     return result;
37730                 };
37731
37732                 me.doSort(sorterFn);
37733             }
37734         }
37735
37736         return sorters;
37737     },
37738
37739     onBeforeSort: Ext.emptyFn,
37740
37741     /**
37742      * @private
37743      * Normalizes an array of sorter objects, ensuring that they are all Ext.util.Sorter instances
37744      * @param {Object[]} sorters The sorters array
37745      * @return {Ext.util.Sorter[]} Array of Ext.util.Sorter objects
37746      */
37747     decodeSorters: function(sorters) {
37748         if (!Ext.isArray(sorters)) {
37749             if (sorters === undefined) {
37750                 sorters = [];
37751             } else {
37752                 sorters = [sorters];
37753             }
37754         }
37755
37756         var length = sorters.length,
37757             Sorter = Ext.util.Sorter,
37758             fields = this.model ? this.model.prototype.fields : null,
37759             field,
37760             config, i;
37761
37762         for (i = 0; i < length; i++) {
37763             config = sorters[i];
37764
37765             if (!(config instanceof Sorter)) {
37766                 if (Ext.isString(config)) {
37767                     config = {
37768                         property: config
37769                     };
37770                 }
37771
37772                 Ext.applyIf(config, {
37773                     root     : this.sortRoot,
37774                     direction: "ASC"
37775                 });
37776
37777                 //support for 3.x style sorters where a function can be defined as 'fn'
37778                 if (config.fn) {
37779                     config.sorterFn = config.fn;
37780                 }
37781
37782                 //support a function to be passed as a sorter definition
37783                 if (typeof config == 'function') {
37784                     config = {
37785                         sorterFn: config
37786                     };
37787                 }
37788
37789                 // ensure sortType gets pushed on if necessary
37790                 if (fields && !config.transform) {
37791                     field = fields.get(config.property);
37792                     config.transform = field ? field.sortType : undefined;
37793                 }
37794                 sorters[i] = Ext.create('Ext.util.Sorter', config);
37795             }
37796         }
37797
37798         return sorters;
37799     },
37800
37801     getSorters: function() {
37802         return this.sorters.items;
37803     }
37804 });
37805 /**
37806  * @class Ext.util.MixedCollection
37807  * <p>
37808  * Represents a collection of a set of key and value pairs. Each key in the MixedCollection
37809  * must be unique, the same key cannot exist twice. This collection is ordered, items in the
37810  * collection can be accessed by index  or via the key. Newly added items are added to
37811  * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it
37812  * is heavier and provides more functionality. Sample usage:
37813  * <pre><code>
37814 var coll = new Ext.util.MixedCollection();
37815 coll.add('key1', 'val1');
37816 coll.add('key2', 'val2');
37817 coll.add('key3', 'val3');
37818
37819 console.log(coll.get('key1')); // prints 'val1'
37820 console.log(coll.indexOfKey('key3')); // prints 2
37821  * </code></pre>
37822  *
37823  * <p>
37824  * The MixedCollection also has support for sorting and filtering of the values in the collection.
37825  * <pre><code>
37826 var coll = new Ext.util.MixedCollection();
37827 coll.add('key1', 100);
37828 coll.add('key2', -100);
37829 coll.add('key3', 17);
37830 coll.add('key4', 0);
37831 var biggerThanZero = coll.filterBy(function(value){
37832     return value > 0;
37833 });
37834 console.log(biggerThanZero.getCount()); // prints 2
37835  * </code></pre>
37836  * </p>
37837  */
37838 Ext.define('Ext.util.MixedCollection', {
37839     extend: 'Ext.util.AbstractMixedCollection',
37840     mixins: {
37841         sortable: 'Ext.util.Sortable'
37842     },
37843
37844     /**
37845      * Creates new MixedCollection.
37846      * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
37847      * function should add function references to the collection. Defaults to
37848      * <tt>false</tt>.
37849      * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
37850      * and return the key value for that item.  This is used when available to look up the key on items that
37851      * were passed without an explicit key parameter to a MixedCollection method.  Passing this parameter is
37852      * equivalent to providing an implementation for the {@link #getKey} method.
37853      */
37854     constructor: function() {
37855         var me = this;
37856         me.callParent(arguments);
37857         me.addEvents('sort');
37858         me.mixins.sortable.initSortable.call(me);
37859     },
37860
37861     doSort: function(sorterFn) {
37862         this.sortBy(sorterFn);
37863     },
37864
37865     /**
37866      * @private
37867      * Performs the actual sorting based on a direction and a sorting function. Internally,
37868      * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
37869      * the sorted array data back into this.items and this.keys
37870      * @param {String} property Property to sort by ('key', 'value', or 'index')
37871      * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
37872      * @param {Function} fn (optional) Comparison function that defines the sort order.
37873      * Defaults to sorting by numeric value.
37874      */
37875     _sort : function(property, dir, fn){
37876         var me = this,
37877             i, len,
37878             dsc   = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
37879
37880             //this is a temporary array used to apply the sorting function
37881             c     = [],
37882             keys  = me.keys,
37883             items = me.items;
37884
37885         //default to a simple sorter function if one is not provided
37886         fn = fn || function(a, b) {
37887             return a - b;
37888         };
37889
37890         //copy all the items into a temporary array, which we will sort
37891         for(i = 0, len = items.length; i < len; i++){
37892             c[c.length] = {
37893                 key  : keys[i],
37894                 value: items[i],
37895                 index: i
37896             };
37897         }
37898
37899         //sort the temporary array
37900         Ext.Array.sort(c, function(a, b){
37901             var v = fn(a[property], b[property]) * dsc;
37902             if(v === 0){
37903                 v = (a.index < b.index ? -1 : 1);
37904             }
37905             return v;
37906         });
37907
37908         //copy the temporary array back into the main this.items and this.keys objects
37909         for(i = 0, len = c.length; i < len; i++){
37910             items[i] = c[i].value;
37911             keys[i]  = c[i].key;
37912         }
37913
37914         me.fireEvent('sort', me);
37915     },
37916
37917     /**
37918      * Sorts the collection by a single sorter function
37919      * @param {Function} sorterFn The function to sort by
37920      */
37921     sortBy: function(sorterFn) {
37922         var me     = this,
37923             items  = me.items,
37924             keys   = me.keys,
37925             length = items.length,
37926             temp   = [],
37927             i;
37928
37929         //first we create a copy of the items array so that we can sort it
37930         for (i = 0; i < length; i++) {
37931             temp[i] = {
37932                 key  : keys[i],
37933                 value: items[i],
37934                 index: i
37935             };
37936         }
37937
37938         Ext.Array.sort(temp, function(a, b) {
37939             var v = sorterFn(a.value, b.value);
37940             if (v === 0) {
37941                 v = (a.index < b.index ? -1 : 1);
37942             }
37943
37944             return v;
37945         });
37946
37947         //copy the temporary array back into the main this.items and this.keys objects
37948         for (i = 0; i < length; i++) {
37949             items[i] = temp[i].value;
37950             keys[i]  = temp[i].key;
37951         }
37952         
37953         me.fireEvent('sort', me, items, keys);
37954     },
37955
37956     /**
37957      * Reorders each of the items based on a mapping from old index to new index. Internally this
37958      * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
37959      * @param {Object} mapping Mapping from old item index to new item index
37960      */
37961     reorder: function(mapping) {
37962         var me = this,
37963             items = me.items,
37964             index = 0,
37965             length = items.length,
37966             order = [],
37967             remaining = [],
37968             oldIndex;
37969
37970         me.suspendEvents();
37971
37972         //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
37973         for (oldIndex in mapping) {
37974             order[mapping[oldIndex]] = items[oldIndex];
37975         }
37976
37977         for (index = 0; index < length; index++) {
37978             if (mapping[index] == undefined) {
37979                 remaining.push(items[index]);
37980             }
37981         }
37982
37983         for (index = 0; index < length; index++) {
37984             if (order[index] == undefined) {
37985                 order[index] = remaining.shift();
37986             }
37987         }
37988
37989         me.clear();
37990         me.addAll(order);
37991
37992         me.resumeEvents();
37993         me.fireEvent('sort', me);
37994     },
37995
37996     /**
37997      * Sorts this collection by <b>key</b>s.
37998      * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
37999      * @param {Function} fn (optional) Comparison function that defines the sort order.
38000      * Defaults to sorting by case insensitive string.
38001      */
38002     sortByKey : function(dir, fn){
38003         this._sort('key', dir, fn || function(a, b){
38004             var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
38005             return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
38006         });
38007     }
38008 });
38009
38010 /**
38011  * @author Ed Spencer
38012  * @class Ext.data.Errors
38013  * @extends Ext.util.MixedCollection
38014  *
38015  * <p>Wraps a collection of validation error responses and provides convenient functions for
38016  * accessing and errors for specific fields.</p>
38017  *
38018  * <p>Usually this class does not need to be instantiated directly - instances are instead created
38019  * automatically when {@link Ext.data.Model#validate validate} on a model instance:</p>
38020  *
38021 <pre><code>
38022 //validate some existing model instance - in this case it returned 2 failures messages
38023 var errors = myModel.validate();
38024
38025 errors.isValid(); //false
38026
38027 errors.length; //2
38028 errors.getByField('name');  // [{field: 'name',  message: 'must be present'}]
38029 errors.getByField('title'); // [{field: 'title', message: 'is too short'}]
38030 </code></pre>
38031  */
38032 Ext.define('Ext.data.Errors', {
38033     extend: 'Ext.util.MixedCollection',
38034
38035     /**
38036      * Returns true if there are no errors in the collection
38037      * @return {Boolean}
38038      */
38039     isValid: function() {
38040         return this.length === 0;
38041     },
38042
38043     /**
38044      * Returns all of the errors for the given field
38045      * @param {String} fieldName The field to get errors for
38046      * @return {Object[]} All errors for the given field
38047      */
38048     getByField: function(fieldName) {
38049         var errors = [],
38050             error, field, i;
38051
38052         for (i = 0; i < this.length; i++) {
38053             error = this.items[i];
38054
38055             if (error.field == fieldName) {
38056                 errors.push(error);
38057             }
38058         }
38059
38060         return errors;
38061     }
38062 });
38063
38064 /**
38065  * @author Ed Spencer
38066  *
38067  * Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link
38068  * Ext.data.Store Store} - often in response to an AJAX request. In general there is usually no need to create
38069  * a Reader instance directly, since a Reader is almost always used together with a {@link Ext.data.proxy.Proxy Proxy},
38070  * and is configured using the Proxy's {@link Ext.data.proxy.Proxy#cfg-reader reader} configuration property:
38071  * 
38072  *     Ext.create('Ext.data.Store', {
38073  *         model: 'User',
38074  *         proxy: {
38075  *             type: 'ajax',
38076  *             url : 'users.json',
38077  *             reader: {
38078  *                 type: 'json',
38079  *                 root: 'users'
38080  *             }
38081  *         },
38082  *     });
38083  *     
38084  * The above reader is configured to consume a JSON string that looks something like this:
38085  *  
38086  *     {
38087  *         "success": true,
38088  *         "users": [
38089  *             { "name": "User 1" },
38090  *             { "name": "User 2" }
38091  *         ]
38092  *     }
38093  * 
38094  *
38095  * # Loading Nested Data
38096  *
38097  * Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.Association
38098  * associations} configured on each Model. Below is an example demonstrating the flexibility of these associations in a
38099  * fictional CRM system which manages a User, their Orders, OrderItems and Products. First we'll define the models:
38100  *
38101  *     Ext.define("User", {
38102  *         extend: 'Ext.data.Model',
38103  *         fields: [
38104  *             'id', 'name'
38105  *         ],
38106  *
38107  *         hasMany: {model: 'Order', name: 'orders'},
38108  *
38109  *         proxy: {
38110  *             type: 'rest',
38111  *             url : 'users.json',
38112  *             reader: {
38113  *                 type: 'json',
38114  *                 root: 'users'
38115  *             }
38116  *         }
38117  *     });
38118  *
38119  *     Ext.define("Order", {
38120  *         extend: 'Ext.data.Model',
38121  *         fields: [
38122  *             'id', 'total'
38123  *         ],
38124  *
38125  *         hasMany  : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
38126  *         belongsTo: 'User'
38127  *     });
38128  *
38129  *     Ext.define("OrderItem", {
38130  *         extend: 'Ext.data.Model',
38131  *         fields: [
38132  *             'id', 'price', 'quantity', 'order_id', 'product_id'
38133  *         ],
38134  *
38135  *         belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
38136  *     });
38137  *
38138  *     Ext.define("Product", {
38139  *         extend: 'Ext.data.Model',
38140  *         fields: [
38141  *             'id', 'name'
38142  *         ],
38143  *
38144  *         hasMany: 'OrderItem'
38145  *     });
38146  *
38147  * This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems.
38148  * Finally, each OrderItem has a single Product. This allows us to consume data like this:
38149  *
38150  *     {
38151  *         "users": [
38152  *             {
38153  *                 "id": 123,
38154  *                 "name": "Ed",
38155  *                 "orders": [
38156  *                     {
38157  *                         "id": 50,
38158  *                         "total": 100,
38159  *                         "order_items": [
38160  *                             {
38161  *                                 "id"      : 20,
38162  *                                 "price"   : 40,
38163  *                                 "quantity": 2,
38164  *                                 "product" : {
38165  *                                     "id": 1000,
38166  *                                     "name": "MacBook Pro"
38167  *                                 }
38168  *                             },
38169  *                             {
38170  *                                 "id"      : 21,
38171  *                                 "price"   : 20,
38172  *                                 "quantity": 3,
38173  *                                 "product" : {
38174  *                                     "id": 1001,
38175  *                                     "name": "iPhone"
38176  *                                 }
38177  *                             }
38178  *                         ]
38179  *                     }
38180  *                 ]
38181  *             }
38182  *         ]
38183  *     }
38184  *
38185  * The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the
38186  * Orders for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case),
38187  * and finally the Product associated with each OrderItem. Now we can read the data and use it as follows:
38188  *
38189  *     var store = Ext.create('Ext.data.Store', {
38190  *         model: "User"
38191  *     });
38192  *
38193  *     store.load({
38194  *         callback: function() {
38195  *             //the user that was loaded
38196  *             var user = store.first();
38197  *
38198  *             console.log("Orders for " + user.get('name') + ":")
38199  *
38200  *             //iterate over the Orders for each User
38201  *             user.orders().each(function(order) {
38202  *                 console.log("Order ID: " + order.getId() + ", which contains items:");
38203  *
38204  *                 //iterate over the OrderItems for each Order
38205  *                 order.orderItems().each(function(orderItem) {
38206  *                     //we know that the Product data is already loaded, so we can use the synchronous getProduct
38207  *                     //usually, we would use the asynchronous version (see {@link Ext.data.BelongsToAssociation})
38208  *                     var product = orderItem.getProduct();
38209  *
38210  *                     console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
38211  *                 });
38212  *             });
38213  *         }
38214  *     });
38215  *
38216  * Running the code above results in the following:
38217  *
38218  *     Orders for Ed:
38219  *     Order ID: 50, which contains items:
38220  *     2 orders of MacBook Pro
38221  *     3 orders of iPhone
38222  */
38223 Ext.define('Ext.data.reader.Reader', {
38224     requires: ['Ext.data.ResultSet'],
38225     alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
38226     
38227     /**
38228      * @cfg {String} idProperty
38229      * Name of the property within a row object that contains a record identifier value. Defaults to The id of the
38230      * model. If an idProperty is explicitly specified it will override that of the one specified on the model
38231      */
38232
38233     /**
38234      * @cfg {String} totalProperty
38235      * Name of the property from which to retrieve the total number of records in the dataset. This is only needed if
38236      * the whole dataset is not passed in one go, but is being paged from the remote server. Defaults to total.
38237      */
38238     totalProperty: 'total',
38239
38240     /**
38241      * @cfg {String} successProperty
38242      * Name of the property from which to retrieve the success attribute. Defaults to success. See
38243      * {@link Ext.data.proxy.Server}.{@link Ext.data.proxy.Server#exception exception} for additional information.
38244      */
38245     successProperty: 'success',
38246
38247     /**
38248      * @cfg {String} root
38249      * The name of the property which contains the Array of row objects.  For JSON reader it's dot-separated list
38250      * of property names.  For XML reader it's a CSS selector.  For array reader it's not applicable.
38251      * 
38252      * By default the natural root of the data will be used.  The root Json array, the root XML element, or the array.
38253      *
38254      * The data packet value for this property should be an empty array to clear the data or show no data.
38255      */
38256     root: '',
38257     
38258     /**
38259      * @cfg {String} messageProperty
38260      * The name of the property which contains a response message. This property is optional.
38261      */
38262     
38263     /**
38264      * @cfg {Boolean} implicitIncludes
38265      * True to automatically parse models nested within other models in a response object. See the
38266      * Ext.data.reader.Reader intro docs for full explanation. Defaults to true.
38267      */
38268     implicitIncludes: true,
38269     
38270     isReader: true,
38271     
38272     /**
38273      * Creates new Reader.
38274      * @param {Object} config (optional) Config object.
38275      */
38276     constructor: function(config) {
38277         var me = this;
38278         
38279         Ext.apply(me, config || {});
38280         me.fieldCount = 0;
38281         me.model = Ext.ModelManager.getModel(config.model);
38282         if (me.model) {
38283             me.buildExtractors();
38284         }
38285     },
38286
38287     /**
38288      * Sets a new model for the reader.
38289      * @private
38290      * @param {Object} model The model to set.
38291      * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
38292      */
38293     setModel: function(model, setOnProxy) {
38294         var me = this;
38295         
38296         me.model = Ext.ModelManager.getModel(model);
38297         me.buildExtractors(true);
38298         
38299         if (setOnProxy && me.proxy) {
38300             me.proxy.setModel(me.model, true);
38301         }
38302     },
38303
38304     /**
38305      * Reads the given response object. This method normalizes the different types of response object that may be passed
38306      * to it, before handing off the reading of records to the {@link #readRecords} function.
38307      * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
38308      * @return {Ext.data.ResultSet} The parsed ResultSet object
38309      */
38310     read: function(response) {
38311         var data = response;
38312         
38313         if (response && response.responseText) {
38314             data = this.getResponseData(response);
38315         }
38316         
38317         if (data) {
38318             return this.readRecords(data);
38319         } else {
38320             return this.nullResultSet;
38321         }
38322     },
38323
38324     /**
38325      * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call this function
38326      * before running its own logic and returning the Ext.data.ResultSet instance. For most Readers additional
38327      * processing should not be needed.
38328      * @param {Object} data The raw data object
38329      * @return {Ext.data.ResultSet} A ResultSet object
38330      */
38331     readRecords: function(data) {
38332         var me  = this;
38333         
38334         /*
38335          * We check here whether the number of fields has changed since the last read.
38336          * This works around an issue when a Model is used for both a Tree and another
38337          * source, because the tree decorates the model with extra fields and it causes
38338          * issues because the readers aren't notified.
38339          */
38340         if (me.fieldCount !== me.getFields().length) {
38341             me.buildExtractors(true);
38342         }
38343         
38344         /**
38345          * @property {Object} rawData
38346          * The raw data object that was last passed to readRecords. Stored for further processing if needed
38347          */
38348         me.rawData = data;
38349
38350         data = me.getData(data);
38351
38352         // If we pass an array as the data, we dont use getRoot on the data.
38353         // Instead the root equals to the data.
38354         var root    = Ext.isArray(data) ? data : me.getRoot(data),
38355             success = true,
38356             recordCount = 0,
38357             total, value, records, message;
38358             
38359         if (root) {
38360             total = root.length;
38361         }
38362
38363         if (me.totalProperty) {
38364             value = parseInt(me.getTotal(data), 10);
38365             if (!isNaN(value)) {
38366                 total = value;
38367             }
38368         }
38369
38370         if (me.successProperty) {
38371             value = me.getSuccess(data);
38372             if (value === false || value === 'false') {
38373                 success = false;
38374             }
38375         }
38376         
38377         if (me.messageProperty) {
38378             message = me.getMessage(data);
38379         }
38380         
38381         if (root) {
38382             records = me.extractData(root);
38383             recordCount = records.length;
38384         } else {
38385             recordCount = 0;
38386             records = [];
38387         }
38388
38389         return Ext.create('Ext.data.ResultSet', {
38390             total  : total || recordCount,
38391             count  : recordCount,
38392             records: records,
38393             success: success,
38394             message: message
38395         });
38396     },
38397
38398     /**
38399      * Returns extracted, type-cast rows of data.  Iterates to call #extractValues for each row
38400      * @param {Object[]/Object} root from server response
38401      * @private
38402      */
38403     extractData : function(root) {
38404         var me = this,
38405             values  = [],
38406             records = [],
38407             Model   = me.model,
38408             i       = 0,
38409             length  = root.length,
38410             idProp  = me.getIdProperty(),
38411             node, id, record;
38412             
38413         if (!root.length && Ext.isObject(root)) {
38414             root = [root];
38415             length = 1;
38416         }
38417
38418         for (; i < length; i++) {
38419             node   = root[i];
38420             values = me.extractValues(node);
38421             id     = me.getId(node);
38422
38423             
38424             record = new Model(values, id, node);
38425             records.push(record);
38426                 
38427             if (me.implicitIncludes) {
38428                 me.readAssociated(record, node);
38429             }
38430         }
38431
38432         return records;
38433     },
38434     
38435     /**
38436      * @private
38437      * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
38438      * on the record provided.
38439      * @param {Ext.data.Model} record The record to load associations for
38440      * @param {Object} data The data object
38441      * @return {String} Return value description
38442      */
38443     readAssociated: function(record, data) {
38444         var associations = record.associations.items,
38445             i            = 0,
38446             length       = associations.length,
38447             association, associationData, proxy, reader;
38448         
38449         for (; i < length; i++) {
38450             association     = associations[i];
38451             associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
38452             
38453             if (associationData) {
38454                 reader = association.getReader();
38455                 if (!reader) {
38456                     proxy = association.associatedModel.proxy;
38457                     // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
38458                     if (proxy) {
38459                         reader = proxy.getReader();
38460                     } else {
38461                         reader = new this.constructor({
38462                             model: association.associatedName
38463                         });
38464                     }
38465                 }
38466                 association.read(record, reader, associationData);
38467             }  
38468         }
38469     },
38470     
38471     /**
38472      * @private
38473      * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
38474      * record, this should return the relevant part of that data for the given association name. This is only really
38475      * needed to support the XML Reader, which has to do a query to get the associated data object
38476      * @param {Object} data The raw data object
38477      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
38478      * @return {Object} The root
38479      */
38480     getAssociatedDataRoot: function(data, associationName) {
38481         return data[associationName];
38482     },
38483     
38484     getFields: function() {
38485         return this.model.prototype.fields.items;
38486     },
38487
38488     /**
38489      * @private
38490      * Given an object representing a single model instance's data, iterates over the model's fields and
38491      * builds an object with the value for each field.
38492      * @param {Object} data The data object to convert
38493      * @return {Object} Data object suitable for use with a model constructor
38494      */
38495     extractValues: function(data) {
38496         var fields = this.getFields(),
38497             i      = 0,
38498             length = fields.length,
38499             output = {},
38500             field, value;
38501
38502         for (; i < length; i++) {
38503             field = fields[i];
38504             value = this.extractorFunctions[i](data);
38505
38506             output[field.name] = value;
38507         }
38508
38509         return output;
38510     },
38511
38512     /**
38513      * @private
38514      * By default this function just returns what is passed to it. It can be overridden in a subclass
38515      * to return something else. See XmlReader for an example.
38516      * @param {Object} data The data object
38517      * @return {Object} The normalized data object
38518      */
38519     getData: function(data) {
38520         return data;
38521     },
38522
38523     /**
38524      * @private
38525      * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
38526      * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
38527      * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
38528      * @param {Object} data The data object
38529      * @return {Object} The same data object
38530      */
38531     getRoot: function(data) {
38532         return data;
38533     },
38534
38535     /**
38536      * Takes a raw response object (as passed to this.read) and returns the useful data segment of it. This must be
38537      * implemented by each subclass
38538      * @param {Object} response The responce object
38539      * @return {Object} The useful data from the response
38540      */
38541     getResponseData: function(response) {
38542     },
38543
38544     /**
38545      * @private
38546      * Reconfigures the meta data tied to this Reader
38547      */
38548     onMetaChange : function(meta) {
38549         var fields = meta.fields,
38550             newModel;
38551         
38552         Ext.apply(this, meta);
38553         
38554         if (fields) {
38555             newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
38556                 extend: 'Ext.data.Model',
38557                 fields: fields
38558             });
38559             this.setModel(newModel, true);
38560         } else {
38561             this.buildExtractors(true);
38562         }
38563     },
38564     
38565     /**
38566      * Get the idProperty to use for extracting data
38567      * @private
38568      * @return {String} The id property
38569      */
38570     getIdProperty: function(){
38571         var prop = this.idProperty;
38572         if (Ext.isEmpty(prop)) {
38573             prop = this.model.prototype.idProperty;
38574         }
38575         return prop;
38576     },
38577
38578     /**
38579      * @private
38580      * This builds optimized functions for retrieving record data and meta data from an object.
38581      * Subclasses may need to implement their own getRoot function.
38582      * @param {Boolean} [force=false] True to automatically remove existing extractor functions first
38583      */
38584     buildExtractors: function(force) {
38585         var me          = this,
38586             idProp      = me.getIdProperty(),
38587             totalProp   = me.totalProperty,
38588             successProp = me.successProperty,
38589             messageProp = me.messageProperty,
38590             accessor;
38591             
38592         if (force === true) {
38593             delete me.extractorFunctions;
38594         }
38595         
38596         if (me.extractorFunctions) {
38597             return;
38598         }   
38599
38600         //build the extractors for all the meta data
38601         if (totalProp) {
38602             me.getTotal = me.createAccessor(totalProp);
38603         }
38604
38605         if (successProp) {
38606             me.getSuccess = me.createAccessor(successProp);
38607         }
38608
38609         if (messageProp) {
38610             me.getMessage = me.createAccessor(messageProp);
38611         }
38612
38613         if (idProp) {
38614             accessor = me.createAccessor(idProp);
38615
38616             me.getId = function(record) {
38617                 var id = accessor.call(me, record);
38618                 return (id === undefined || id === '') ? null : id;
38619             };
38620         } else {
38621             me.getId = function() {
38622                 return null;
38623             };
38624         }
38625         me.buildFieldExtractors();
38626     },
38627
38628     /**
38629      * @private
38630      */
38631     buildFieldExtractors: function() {
38632         //now build the extractors for all the fields
38633         var me = this,
38634             fields = me.getFields(),
38635             ln = fields.length,
38636             i  = 0,
38637             extractorFunctions = [],
38638             field, map;
38639
38640         for (; i < ln; i++) {
38641             field = fields[i];
38642             map   = (field.mapping !== undefined && field.mapping !== null) ? field.mapping : field.name;
38643
38644             extractorFunctions.push(me.createAccessor(map));
38645         }
38646         me.fieldCount = ln;
38647
38648         me.extractorFunctions = extractorFunctions;
38649     }
38650 }, function() {
38651     Ext.apply(this, {
38652         // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
38653         nullResultSet: Ext.create('Ext.data.ResultSet', {
38654             total  : 0,
38655             count  : 0,
38656             records: [],
38657             success: true
38658         })
38659     });
38660 });
38661 /**
38662  * @author Ed Spencer
38663  * @class Ext.data.reader.Json
38664  * @extends Ext.data.reader.Reader
38665  *
38666  * <p>The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually
38667  * happens as a result of loading a Store - for example we might create something like this:</p>
38668  *
38669 <pre><code>
38670 Ext.define('User', {
38671     extend: 'Ext.data.Model',
38672     fields: ['id', 'name', 'email']
38673 });
38674
38675 var store = Ext.create('Ext.data.Store', {
38676     model: 'User',
38677     proxy: {
38678         type: 'ajax',
38679         url : 'users.json',
38680         reader: {
38681             type: 'json'
38682         }
38683     }
38684 });
38685 </code></pre>
38686  *
38687  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
38688  * not already familiar with them.</p>
38689  *
38690  * <p>We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s
38691  * {@link Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
38692  * Store, so it is as if we passed this instead:
38693  *
38694 <pre><code>
38695 reader: {
38696     type : 'json',
38697     model: 'User'
38698 }
38699 </code></pre>
38700  *
38701  * <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>
38702  *
38703 <pre><code>
38704 [
38705     {
38706         "id": 1,
38707         "name": "Ed Spencer",
38708         "email": "ed@sencha.com"
38709     },
38710     {
38711         "id": 2,
38712         "name": "Abe Elias",
38713         "email": "abe@sencha.com"
38714     }
38715 ]
38716 </code></pre>
38717  *
38718  * <p><u>Reading other JSON formats</u></p>
38719  *
38720  * <p>If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually
38721  * pass JsonReader a couple of configuration options to make it parse your format. For example, we can use the
38722  * {@link #root} configuration to parse data that comes back like this:</p>
38723  *
38724 <pre><code>
38725 {
38726     "users": [
38727        {
38728            "id": 1,
38729            "name": "Ed Spencer",
38730            "email": "ed@sencha.com"
38731        },
38732        {
38733            "id": 2,
38734            "name": "Abe Elias",
38735            "email": "abe@sencha.com"
38736        }
38737     ]
38738 }
38739 </code></pre>
38740  *
38741  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
38742  *
38743 <pre><code>
38744 reader: {
38745     type: 'json',
38746     root: 'users'
38747 }
38748 </code></pre>
38749  *
38750  * <p>Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata
38751  * around each record inside a nested structure like this:</p>
38752  *
38753 <pre><code>
38754 {
38755     "total": 122,
38756     "offset": 0,
38757     "users": [
38758         {
38759             "id": "ed-spencer-1",
38760             "value": 1,
38761             "user": {
38762                 "id": 1,
38763                 "name": "Ed Spencer",
38764                 "email": "ed@sencha.com"
38765             }
38766         }
38767     ]
38768 }
38769 </code></pre>
38770  *
38771  * <p>In the case above the record data is nested an additional level inside the "users" array as each "user" item has
38772  * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the
38773  * JSON above we need to specify the {@link #record} configuration like this:</p>
38774  *
38775 <pre><code>
38776 reader: {
38777     type  : 'json',
38778     root  : 'users',
38779     record: 'user'
38780 }
38781 </code></pre>
38782  *
38783  * <p><u>Response metadata</u></p>
38784  *
38785  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
38786  * and the {@link #successProperty success status of the response}. These are typically included in the JSON response
38787  * like this:</p>
38788  *
38789 <pre><code>
38790 {
38791     "total": 100,
38792     "success": true,
38793     "users": [
38794         {
38795             "id": 1,
38796             "name": "Ed Spencer",
38797             "email": "ed@sencha.com"
38798         }
38799     ]
38800 }
38801 </code></pre>
38802  *
38803  * <p>If these properties are present in the JSON response they can be parsed out by the JsonReader and used by the
38804  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
38805  * options:</p>
38806  *
38807 <pre><code>
38808 reader: {
38809     type : 'json',
38810     root : 'users',
38811     totalProperty  : 'total',
38812     successProperty: 'success'
38813 }
38814 </code></pre>
38815  *
38816  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
38817  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
38818  * returned.</p>
38819  */
38820 Ext.define('Ext.data.reader.Json', {
38821     extend: 'Ext.data.reader.Reader',
38822     alternateClassName: 'Ext.data.JsonReader',
38823     alias : 'reader.json',
38824
38825     root: '',
38826
38827     /**
38828      * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.
38829      * See the JsonReader intro docs for more details. This is not often needed.
38830      */
38831
38832     /**
38833      * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when
38834      * reading values. Defalts to <tt>false</tt>.
38835      * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar
38836      * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name
38837      * "foo.bar.baz" direct from the root object.
38838      */
38839     useSimpleAccessors: false,
38840
38841     /**
38842      * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to
38843      * retrieve meta data from the response, and extractData to turn the JSON data into model instances.
38844      * @param {Object} data The raw JSON data
38845      * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results
38846      */
38847     readRecords: function(data) {
38848         //this has to be before the call to super because we use the meta data in the superclass readRecords
38849         if (data.metaData) {
38850             this.onMetaChange(data.metaData);
38851         }
38852
38853         /**
38854          * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
38855          * @property {Object} jsonData
38856          */
38857         this.jsonData = data;
38858         return this.callParent([data]);
38859     },
38860
38861     //inherit docs
38862     getResponseData: function(response) {
38863         var data;
38864         try {
38865             data = Ext.decode(response.responseText);
38866         }
38867         catch (ex) {
38868             Ext.Error.raise({
38869                 response: response,
38870                 json: response.responseText,
38871                 parseError: ex,
38872                 msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
38873             });
38874         }
38875
38876         return data;
38877     },
38878
38879     //inherit docs
38880     buildExtractors : function() {
38881         var me = this;
38882
38883         me.callParent(arguments);
38884
38885         if (me.root) {
38886             me.getRoot = me.createAccessor(me.root);
38887         } else {
38888             me.getRoot = function(root) {
38889                 return root;
38890             };
38891         }
38892     },
38893
38894     /**
38895      * @private
38896      * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
38897      * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
38898      * @param {Object} root The JSON root node
38899      * @return {Ext.data.Model[]} The records
38900      */
38901     extractData: function(root) {
38902         var recordName = this.record,
38903             data = [],
38904             length, i;
38905
38906         if (recordName) {
38907             length = root.length;
38908             
38909             if (!length && Ext.isObject(root)) {
38910                 length = 1;
38911                 root = [root];
38912             }
38913
38914             for (i = 0; i < length; i++) {
38915                 data[i] = root[i][recordName];
38916             }
38917         } else {
38918             data = root;
38919         }
38920         return this.callParent([data]);
38921     },
38922
38923     /**
38924      * @private
38925      * Returns an accessor function for the given property string. Gives support for properties such as the following:
38926      * 'someProperty'
38927      * 'some.property'
38928      * 'some["property"]'
38929      * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
38930      */
38931     createAccessor: function() {
38932         var re = /[\[\.]/;
38933
38934         return function(expr) {
38935             if (Ext.isEmpty(expr)) {
38936                 return Ext.emptyFn;
38937             }
38938             if (Ext.isFunction(expr)) {
38939                 return expr;
38940             }
38941             if (this.useSimpleAccessors !== true) {
38942                 var i = String(expr).search(re);
38943                 if (i >= 0) {
38944                     return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
38945                 }
38946             }
38947             return function(obj) {
38948                 return obj[expr];
38949             };
38950         };
38951     }()
38952 });
38953 /**
38954  * @class Ext.data.writer.Json
38955  * @extends Ext.data.writer.Writer
38956
38957 This class is used to write {@link Ext.data.Model} data to the server in a JSON format.
38958 The {@link #allowSingle} configuration can be set to false to force the records to always be
38959 encoded in an array, even if there is only a single record being sent.
38960
38961  * @markdown
38962  */
38963 Ext.define('Ext.data.writer.Json', {
38964     extend: 'Ext.data.writer.Writer',
38965     alternateClassName: 'Ext.data.JsonWriter',
38966     alias: 'writer.json',
38967     
38968     /**
38969      * @cfg {String} root The key under which the records in this Writer will be placed. Defaults to <tt>undefined</tt>.
38970      * Example generated request, using root: 'records':
38971 <pre><code>
38972 {'records': [{name: 'my record'}, {name: 'another record'}]}
38973 </code></pre>
38974      */
38975     root: undefined,
38976     
38977     /**
38978      * @cfg {Boolean} encode True to use Ext.encode() on the data before sending. Defaults to <tt>false</tt>.
38979      * The encode option should only be set to true when a {@link #root} is defined, because the values will be
38980      * sent as part of the request parameters as opposed to a raw post. The root will be the name of the parameter
38981      * sent to the server.
38982      */
38983     encode: false,
38984     
38985     /**
38986      * @cfg {Boolean} allowSingle False to ensure that records are always wrapped in an array, even if there is only
38987      * one record being sent. When there is more than one record, they will always be encoded into an array.
38988      * Defaults to <tt>true</tt>. Example:
38989      * <pre><code>
38990 // with allowSingle: true
38991 "root": {
38992     "first": "Mark",
38993     "last": "Corrigan"
38994 }
38995
38996 // with allowSingle: false
38997 "root": [{
38998     "first": "Mark",
38999     "last": "Corrigan"
39000 }]
39001      * </code></pre>
39002      */
39003     allowSingle: true,
39004     
39005     //inherit docs
39006     writeRecords: function(request, data) {
39007         var root = this.root;
39008         
39009         if (this.allowSingle && data.length == 1) {
39010             // convert to single object format
39011             data = data[0];
39012         }
39013         
39014         if (this.encode) {
39015             if (root) {
39016                 // sending as a param, need to encode
39017                 request.params[root] = Ext.encode(data);
39018             } else {
39019             }
39020         } else {
39021             // send as jsonData
39022             request.jsonData = request.jsonData || {};
39023             if (root) {
39024                 request.jsonData[root] = data;
39025             } else {
39026                 request.jsonData = data;
39027             }
39028         }
39029         return request;
39030     }
39031 });
39032
39033 /**
39034  * @author Ed Spencer
39035  *
39036  * Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of {@link Ext.data.Model Model}
39037  * data. Usually developers will not need to create or interact with proxies directly.
39038  *
39039  * # Types of Proxy
39040  *
39041  * There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and {@link Ext.data.proxy.Server Server}.
39042  * The Client proxies save their data locally and include the following subclasses:
39043  *
39044  * - {@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage if the browser supports it
39045  * - {@link Ext.data.proxy.SessionStorage SessionStorageProxy} - saves its data to sessionStorage if the browsers supports it
39046  * - {@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost when the page is refreshed
39047  *
39048  * The Server proxies save their data by sending requests to some remote server. These proxies include:
39049  *
39050  * - {@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain
39051  * - {@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different domain
39052  * - {@link Ext.data.proxy.Direct Direct} - uses {@link Ext.direct.Manager} to send requests
39053  *
39054  * Proxies operate on the principle that all operations performed are either Create, Read, Update or Delete. These four
39055  * operations are mapped to the methods {@link #create}, {@link #read}, {@link #update} and {@link #destroy}
39056  * respectively. Each Proxy subclass implements these functions.
39057  *
39058  * The CRUD methods each expect an {@link Ext.data.Operation Operation} object as the sole argument. The Operation
39059  * encapsulates information about the action the Store wishes to perform, the {@link Ext.data.Model model} instances
39060  * that are to be modified, etc. See the {@link Ext.data.Operation Operation} documentation for more details. Each CRUD
39061  * method also accepts a callback function to be called asynchronously on completion.
39062  *
39063  * Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked by the {@link #batch}
39064  * method.
39065  */
39066 Ext.define('Ext.data.proxy.Proxy', {
39067     alias: 'proxy.proxy',
39068     alternateClassName: ['Ext.data.DataProxy', 'Ext.data.Proxy'],
39069     requires: [
39070         'Ext.data.reader.Json',
39071         'Ext.data.writer.Json'
39072     ],
39073     uses: [
39074         'Ext.data.Batch', 
39075         'Ext.data.Operation', 
39076         'Ext.data.Model'
39077     ],
39078     mixins: {
39079         observable: 'Ext.util.Observable'
39080     },
39081     
39082     /**
39083      * @cfg {String} batchOrder
39084      * Comma-separated ordering 'create', 'update' and 'destroy' actions when batching. Override this to set a different
39085      * order for the batched CRUD actions to be executed in. Defaults to 'create,update,destroy'.
39086      */
39087     batchOrder: 'create,update,destroy',
39088     
39089     /**
39090      * @cfg {Boolean} batchActions
39091      * True to batch actions of a particular type when synchronizing the store. Defaults to true.
39092      */
39093     batchActions: true,
39094     
39095     /**
39096      * @cfg {String} defaultReaderType
39097      * The default registered reader type. Defaults to 'json'.
39098      * @private
39099      */
39100     defaultReaderType: 'json',
39101     
39102     /**
39103      * @cfg {String} defaultWriterType
39104      * The default registered writer type. Defaults to 'json'.
39105      * @private
39106      */
39107     defaultWriterType: 'json',
39108     
39109     /**
39110      * @cfg {String/Ext.data.Model} model
39111      * The name of the Model to tie to this Proxy. Can be either the string name of the Model, or a reference to the
39112      * Model constructor. Required.
39113      */
39114     
39115     /**
39116      * @cfg {Object/String/Ext.data.reader.Reader} reader
39117      * The Ext.data.reader.Reader to use to decode the server's response or data read from client. This can either be a
39118      * Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
39119      */
39120     
39121     /**
39122      * @cfg {Object/String/Ext.data.writer.Writer} writer
39123      * The Ext.data.writer.Writer to use to encode any request sent to the server or saved to client. This can either be
39124      * a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
39125      */
39126     
39127     isProxy: true,
39128     
39129     /**
39130      * Creates the Proxy
39131      * @param {Object} config (optional) Config object.
39132      */
39133     constructor: function(config) {
39134         config = config || {};
39135         
39136         if (config.model === undefined) {
39137             delete config.model;
39138         }
39139
39140         this.mixins.observable.constructor.call(this, config);
39141         
39142         if (this.model !== undefined && !(this.model instanceof Ext.data.Model)) {
39143             this.setModel(this.model);
39144         }
39145     },
39146     
39147     /**
39148      * Sets the model associated with this proxy. This will only usually be called by a Store
39149      *
39150      * @param {String/Ext.data.Model} model The new model. Can be either the model name string,
39151      * or a reference to the model's constructor
39152      * @param {Boolean} setOnStore Sets the new model on the associated Store, if one is present
39153      */
39154     setModel: function(model, setOnStore) {
39155         this.model = Ext.ModelManager.getModel(model);
39156         
39157         var reader = this.reader,
39158             writer = this.writer;
39159         
39160         this.setReader(reader);
39161         this.setWriter(writer);
39162         
39163         if (setOnStore && this.store) {
39164             this.store.setModel(this.model);
39165         }
39166     },
39167     
39168     /**
39169      * Returns the model attached to this Proxy
39170      * @return {Ext.data.Model} The model
39171      */
39172     getModel: function() {
39173         return this.model;
39174     },
39175     
39176     /**
39177      * Sets the Proxy's Reader by string, config object or Reader instance
39178      *
39179      * @param {String/Object/Ext.data.reader.Reader} reader The new Reader, which can be either a type string,
39180      * a configuration object or an Ext.data.reader.Reader instance
39181      * @return {Ext.data.reader.Reader} The attached Reader object
39182      */
39183     setReader: function(reader) {
39184         var me = this;
39185         
39186         if (reader === undefined || typeof reader == 'string') {
39187             reader = {
39188                 type: reader
39189             };
39190         }
39191
39192         if (reader.isReader) {
39193             reader.setModel(me.model);
39194         } else {
39195             Ext.applyIf(reader, {
39196                 proxy: me,
39197                 model: me.model,
39198                 type : me.defaultReaderType
39199             });
39200
39201             reader = Ext.createByAlias('reader.' + reader.type, reader);
39202         }
39203         
39204         me.reader = reader;
39205         return me.reader;
39206     },
39207     
39208     /**
39209      * Returns the reader currently attached to this proxy instance
39210      * @return {Ext.data.reader.Reader} The Reader instance
39211      */
39212     getReader: function() {
39213         return this.reader;
39214     },
39215     
39216     /**
39217      * Sets the Proxy's Writer by string, config object or Writer instance
39218      *
39219      * @param {String/Object/Ext.data.writer.Writer} writer The new Writer, which can be either a type string,
39220      * a configuration object or an Ext.data.writer.Writer instance
39221      * @return {Ext.data.writer.Writer} The attached Writer object
39222      */
39223     setWriter: function(writer) {
39224         if (writer === undefined || typeof writer == 'string') {
39225             writer = {
39226                 type: writer
39227             };
39228         }
39229
39230         if (!(writer instanceof Ext.data.writer.Writer)) {
39231             Ext.applyIf(writer, {
39232                 model: this.model,
39233                 type : this.defaultWriterType
39234             });
39235
39236             writer = Ext.createByAlias('writer.' + writer.type, writer);
39237         }
39238         
39239         this.writer = writer;
39240         
39241         return this.writer;
39242     },
39243     
39244     /**
39245      * Returns the writer currently attached to this proxy instance
39246      * @return {Ext.data.writer.Writer} The Writer instance
39247      */
39248     getWriter: function() {
39249         return this.writer;
39250     },
39251     
39252     /**
39253      * Performs the given create operation.
39254      * @param {Ext.data.Operation} operation The Operation to perform
39255      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39256      * @param {Object} scope Scope to execute the callback function in
39257      * @method
39258      */
39259     create: Ext.emptyFn,
39260     
39261     /**
39262      * Performs the given read operation.
39263      * @param {Ext.data.Operation} operation The Operation to perform
39264      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39265      * @param {Object} scope Scope to execute the callback function in
39266      * @method
39267      */
39268     read: Ext.emptyFn,
39269     
39270     /**
39271      * Performs the given update operation.
39272      * @param {Ext.data.Operation} operation The Operation to perform
39273      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39274      * @param {Object} scope Scope to execute the callback function in
39275      * @method
39276      */
39277     update: Ext.emptyFn,
39278     
39279     /**
39280      * Performs the given destroy operation.
39281      * @param {Ext.data.Operation} operation The Operation to perform
39282      * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
39283      * @param {Object} scope Scope to execute the callback function in
39284      * @method
39285      */
39286     destroy: Ext.emptyFn,
39287     
39288     /**
39289      * Performs a batch of {@link Ext.data.Operation Operations}, in the order specified by {@link #batchOrder}. Used
39290      * internally by {@link Ext.data.Store}'s {@link Ext.data.Store#sync sync} method. Example usage:
39291      *
39292      *     myProxy.batch({
39293      *         create : [myModel1, myModel2],
39294      *         update : [myModel3],
39295      *         destroy: [myModel4, myModel5]
39296      *     });
39297      *
39298      * Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2 are new instances and
39299      * have not been saved before, 3 has been saved previously but needs to be updated, and 4 and 5 have already been
39300      * saved but should now be destroyed.
39301      *
39302      * @param {Object} operations Object containing the Model instances to act upon, keyed by action name
39303      * @param {Object} listeners (optional) listeners object passed straight through to the Batch -
39304      * see {@link Ext.data.Batch}
39305      * @return {Ext.data.Batch} The newly created Ext.data.Batch object
39306      */
39307     batch: function(operations, listeners) {
39308         var me = this,
39309             batch = Ext.create('Ext.data.Batch', {
39310                 proxy: me,
39311                 listeners: listeners || {}
39312             }),
39313             useBatch = me.batchActions, 
39314             records;
39315         
39316         Ext.each(me.batchOrder.split(','), function(action) {
39317             records = operations[action];
39318             if (records) {
39319                 if (useBatch) {
39320                     batch.add(Ext.create('Ext.data.Operation', {
39321                         action: action,
39322                         records: records
39323                     }));
39324                 } else {
39325                     Ext.each(records, function(record){
39326                         batch.add(Ext.create('Ext.data.Operation', {
39327                             action : action, 
39328                             records: [record]
39329                         }));
39330                     });
39331                 }
39332             }
39333         }, me);
39334         
39335         batch.start();
39336         return batch;
39337     }
39338 }, function() {
39339     // Ext.data.proxy.ProxyMgr.registerType('proxy', this);
39340     
39341     //backwards compatibility
39342     Ext.data.DataProxy = this;
39343     // Ext.deprecate('platform', '2.0', function() {
39344     //     Ext.data.DataProxy = this;
39345     // }, this);
39346 });
39347
39348 /**
39349  * @author Ed Spencer
39350  *
39351  * ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, and
39352  * would not usually be used directly.
39353  *
39354  * ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been
39355  * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now
39356  * an alias of AjaxProxy).
39357  * @private
39358  */
39359 Ext.define('Ext.data.proxy.Server', {
39360     extend: 'Ext.data.proxy.Proxy',
39361     alias : 'proxy.server',
39362     alternateClassName: 'Ext.data.ServerProxy',
39363     uses  : ['Ext.data.Request'],
39364
39365     /**
39366      * @cfg {String} url
39367      * The URL from which to request the data object.
39368      */
39369
39370     /**
39371      * @cfg {String} pageParam
39372      * The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to undefined if you don't
39373      * want to send a page parameter.
39374      */
39375     pageParam: 'page',
39376
39377     /**
39378      * @cfg {String} startParam
39379      * The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this to undefined if you don't
39380      * want to send a start parameter.
39381      */
39382     startParam: 'start',
39383
39384     /**
39385      * @cfg {String} limitParam
39386      * The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this to undefined if you don't
39387      * want to send a limit parameter.
39388      */
39389     limitParam: 'limit',
39390
39391     /**
39392      * @cfg {String} groupParam
39393      * The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this to undefined if you don't
39394      * want to send a group parameter.
39395      */
39396     groupParam: 'group',
39397
39398     /**
39399      * @cfg {String} sortParam
39400      * The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this to undefined if you don't
39401      * want to send a sort parameter.
39402      */
39403     sortParam: 'sort',
39404
39405     /**
39406      * @cfg {String} filterParam
39407      * The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set this to undefined if you don't
39408      * want to send a filter parameter.
39409      */
39410     filterParam: 'filter',
39411
39412     /**
39413      * @cfg {String} directionParam
39414      * The name of the direction parameter to send in a request. **This is only used when simpleSortMode is set to
39415      * true.** Defaults to 'dir'.
39416      */
39417     directionParam: 'dir',
39418
39419     /**
39420      * @cfg {Boolean} simpleSortMode
39421      * Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a
39422      * remote sort is requested. The directionParam and sortParam will be sent with the property name and either 'ASC'
39423      * or 'DESC'.
39424      */
39425     simpleSortMode: false,
39426
39427     /**
39428      * @cfg {Boolean} noCache
39429      * Disable caching by adding a unique parameter name to the request. Set to false to allow caching. Defaults to true.
39430      */
39431     noCache : true,
39432
39433     /**
39434      * @cfg {String} cacheString
39435      * The name of the cache param added to the url when using noCache. Defaults to "_dc".
39436      */
39437     cacheString: "_dc",
39438
39439     /**
39440      * @cfg {Number} timeout
39441      * The number of milliseconds to wait for a response. Defaults to 30000 milliseconds (30 seconds).
39442      */
39443     timeout : 30000,
39444
39445     /**
39446      * @cfg {Object} api
39447      * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to:
39448      *
39449      *     api: {
39450      *         create  : undefined,
39451      *         read    : undefined,
39452      *         update  : undefined,
39453      *         destroy : undefined
39454      *     }
39455      *
39456      * The url is built based upon the action being executed [create|read|update|destroy] using the commensurate
39457      * {@link #api} property, or if undefined default to the configured
39458      * {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.
39459      *
39460      * For example:
39461      *
39462      *     api: {
39463      *         create  : '/controller/new',
39464      *         read    : '/controller/load',
39465      *         update  : '/controller/update',
39466      *         destroy : '/controller/destroy_action'
39467      *     }
39468      *
39469      * If the specific URL for a given CRUD action is undefined, the CRUD action request will be directed to the
39470      * configured {@link Ext.data.proxy.Server#url url}.
39471      */
39472
39473     constructor: function(config) {
39474         var me = this;
39475
39476         config = config || {};
39477         this.addEvents(
39478             /**
39479              * @event exception
39480              * Fires when the server returns an exception
39481              * @param {Ext.data.proxy.Proxy} this
39482              * @param {Object} response The response from the AJAX request
39483              * @param {Ext.data.Operation} operation The operation that triggered request
39484              */
39485             'exception'
39486         );
39487         me.callParent([config]);
39488
39489         /**
39490          * @cfg {Object} extraParams
39491          * Extra parameters that will be included on every request. Individual requests with params of the same name
39492          * will override these params when they are in conflict.
39493          */
39494         me.extraParams = config.extraParams || {};
39495
39496         me.api = config.api || {};
39497
39498         //backwards compatibility, will be deprecated in 5.0
39499         me.nocache = me.noCache;
39500     },
39501
39502     //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
39503     create: function() {
39504         return this.doRequest.apply(this, arguments);
39505     },
39506
39507     read: function() {
39508         return this.doRequest.apply(this, arguments);
39509     },
39510
39511     update: function() {
39512         return this.doRequest.apply(this, arguments);
39513     },
39514
39515     destroy: function() {
39516         return this.doRequest.apply(this, arguments);
39517     },
39518
39519     /**
39520      * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
39521      * that this Proxy is attached to.
39522      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
39523      * @return {Ext.data.Request} The request object
39524      */
39525     buildRequest: function(operation) {
39526         var params = Ext.applyIf(operation.params || {}, this.extraParams || {}),
39527             request;
39528
39529         //copy any sorters, filters etc into the params so they can be sent over the wire
39530         params = Ext.applyIf(params, this.getParams(operation));
39531
39532         if (operation.id && !params.id) {
39533             params.id = operation.id;
39534         }
39535
39536         request = Ext.create('Ext.data.Request', {
39537             params   : params,
39538             action   : operation.action,
39539             records  : operation.records,
39540             operation: operation,
39541             url      : operation.url
39542         });
39543
39544         request.url = this.buildUrl(request);
39545
39546         /*
39547          * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the
39548          * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing
39549          */
39550         operation.request = request;
39551
39552         return request;
39553     },
39554
39555     // Should this be documented as protected method?
39556     processResponse: function(success, operation, request, response, callback, scope){
39557         var me = this,
39558             reader,
39559             result;
39560
39561         if (success === true) {
39562             reader = me.getReader();
39563             result = reader.read(me.extractResponseData(response));
39564
39565             if (result.success !== false) {
39566                 //see comment in buildRequest for why we include the response object here
39567                 Ext.apply(operation, {
39568                     response: response,
39569                     resultSet: result
39570                 });
39571
39572                 operation.commitRecords(result.records);
39573                 operation.setCompleted();
39574                 operation.setSuccessful();
39575             } else {
39576                 operation.setException(result.message);
39577                 me.fireEvent('exception', this, response, operation);
39578             }
39579         } else {
39580             me.setException(operation, response);
39581             me.fireEvent('exception', this, response, operation);
39582         }
39583
39584         //this callback is the one that was passed to the 'read' or 'write' function above
39585         if (typeof callback == 'function') {
39586             callback.call(scope || me, operation);
39587         }
39588
39589         me.afterRequest(request, success);
39590     },
39591
39592     /**
39593      * Sets up an exception on the operation
39594      * @private
39595      * @param {Ext.data.Operation} operation The operation
39596      * @param {Object} response The response
39597      */
39598     setException: function(operation, response){
39599         operation.setException({
39600             status: response.status,
39601             statusText: response.statusText
39602         });
39603     },
39604
39605     /**
39606      * Template method to allow subclasses to specify how to get the response for the reader.
39607      * @template
39608      * @private
39609      * @param {Object} response The server response
39610      * @return {Object} The response data to be used by the reader
39611      */
39612     extractResponseData: function(response){
39613         return response;
39614     },
39615
39616     /**
39617      * Encode any values being sent to the server. Can be overridden in subclasses.
39618      * @private
39619      * @param {Array} An array of sorters/filters.
39620      * @return {Object} The encoded value
39621      */
39622     applyEncoding: function(value){
39623         return Ext.encode(value);
39624     },
39625
39626     /**
39627      * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
39628      * this simply JSON-encodes the sorter data
39629      * @param {Ext.util.Sorter[]} sorters The array of {@link Ext.util.Sorter Sorter} objects
39630      * @return {String} The encoded sorters
39631      */
39632     encodeSorters: function(sorters) {
39633         var min = [],
39634             length = sorters.length,
39635             i = 0;
39636
39637         for (; i < length; i++) {
39638             min[i] = {
39639                 property : sorters[i].property,
39640                 direction: sorters[i].direction
39641             };
39642         }
39643         return this.applyEncoding(min);
39644
39645     },
39646
39647     /**
39648      * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
39649      * this simply JSON-encodes the filter data
39650      * @param {Ext.util.Filter[]} filters The array of {@link Ext.util.Filter Filter} objects
39651      * @return {String} The encoded filters
39652      */
39653     encodeFilters: function(filters) {
39654         var min = [],
39655             length = filters.length,
39656             i = 0;
39657
39658         for (; i < length; i++) {
39659             min[i] = {
39660                 property: filters[i].property,
39661                 value   : filters[i].value
39662             };
39663         }
39664         return this.applyEncoding(min);
39665     },
39666
39667     /**
39668      * @private
39669      * Copy any sorters, filters etc into the params so they can be sent over the wire
39670      */
39671     getParams: function(operation) {
39672         var me             = this,
39673             params         = {},
39674             isDef          = Ext.isDefined,
39675             groupers       = operation.groupers,
39676             sorters        = operation.sorters,
39677             filters        = operation.filters,
39678             page           = operation.page,
39679             start          = operation.start,
39680             limit          = operation.limit,
39681
39682             simpleSortMode = me.simpleSortMode,
39683
39684             pageParam      = me.pageParam,
39685             startParam     = me.startParam,
39686             limitParam     = me.limitParam,
39687             groupParam     = me.groupParam,
39688             sortParam      = me.sortParam,
39689             filterParam    = me.filterParam,
39690             directionParam = me.directionParam;
39691
39692         if (pageParam && isDef(page)) {
39693             params[pageParam] = page;
39694         }
39695
39696         if (startParam && isDef(start)) {
39697             params[startParam] = start;
39698         }
39699
39700         if (limitParam && isDef(limit)) {
39701             params[limitParam] = limit;
39702         }
39703
39704         if (groupParam && groupers && groupers.length > 0) {
39705             // Grouper is a subclass of sorter, so we can just use the sorter method
39706             params[groupParam] = me.encodeSorters(groupers);
39707         }
39708
39709         if (sortParam && sorters && sorters.length > 0) {
39710             if (simpleSortMode) {
39711                 params[sortParam] = sorters[0].property;
39712                 params[directionParam] = sorters[0].direction;
39713             } else {
39714                 params[sortParam] = me.encodeSorters(sorters);
39715             }
39716
39717         }
39718
39719         if (filterParam && filters && filters.length > 0) {
39720             params[filterParam] = me.encodeFilters(filters);
39721         }
39722
39723         return params;
39724     },
39725
39726     /**
39727      * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will add the
39728      * cache-buster param to the end of the url. Subclasses may need to perform additional modifications to the url.
39729      * @param {Ext.data.Request} request The request object
39730      * @return {String} The url
39731      */
39732     buildUrl: function(request) {
39733         var me = this,
39734             url = me.getUrl(request);
39735
39736
39737         if (me.noCache) {
39738             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now()));
39739         }
39740
39741         return url;
39742     },
39743
39744     /**
39745      * Get the url for the request taking into account the order of priority,
39746      * - The request
39747      * - The api
39748      * - The url
39749      * @private
39750      * @param {Ext.data.Request} request The request
39751      * @return {String} The url
39752      */
39753     getUrl: function(request){
39754         return request.url || this.api[request.action] || this.url;
39755     },
39756
39757     /**
39758      * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all
39759      * pass through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link
39760      * Ext.data.proxy.JsonP} and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as
39761      * each of the methods that delegate to it.
39762      *
39763      * @param {Ext.data.Operation} operation The Ext.data.Operation object
39764      * @param {Function} callback The callback function to call when the Operation has completed
39765      * @param {Object} scope The scope in which to execute the callback
39766      */
39767     doRequest: function(operation, callback, scope) {
39768     },
39769
39770     /**
39771      * Optional callback function which can be used to clean up after a request has been completed.
39772      * @param {Ext.data.Request} request The Request object
39773      * @param {Boolean} success True if the request was successful
39774      * @method
39775      */
39776     afterRequest: Ext.emptyFn,
39777
39778     onDestroy: function() {
39779         Ext.destroy(this.reader, this.writer);
39780     }
39781 });
39782
39783 /**
39784  * @author Ed Spencer
39785  *
39786  * AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX requests to load
39787  * data from the server, usually to be placed into a {@link Ext.data.Store Store}. Let's take a look at a typical setup.
39788  * 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
39789  * Model}:
39790  *
39791  *     Ext.define('User', {
39792  *         extend: 'Ext.data.Model',
39793  *         fields: ['id', 'name', 'email']
39794  *     });
39795  *
39796  *     //The Store contains the AjaxProxy as an inline configuration
39797  *     var store = Ext.create('Ext.data.Store', {
39798  *         model: 'User',
39799  *         proxy: {
39800  *             type: 'ajax',
39801  *             url : 'users.json'
39802  *         }
39803  *     });
39804  *
39805  *     store.load();
39806  *
39807  * Our example is going to load user data into a Store, so we start off by defining a {@link Ext.data.Model Model} with
39808  * the fields that we expect the server to return. Next we set up the Store itself, along with a
39809  * {@link Ext.data.Store#proxy proxy} configuration. This configuration was automatically turned into an
39810  * Ext.data.proxy.Ajax instance, with the url we specified being passed into AjaxProxy's constructor.
39811  * It's as if we'd done this:
39812  *
39813  *     new Ext.data.proxy.Ajax({
39814  *         url: 'users.json',
39815  *         model: 'User',
39816  *         reader: 'json'
39817  *     });
39818  *
39819  * A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are set by default when we
39820  * create the proxy via the Store - the Store already knows about the Model, and Proxy's default {@link
39821  * Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.
39822  *
39823  * Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we configured
39824  * ('users.json' in this case). As we're performing a read, it sends a GET request to that url (see
39825  * {@link #actionMethods} to customize this - by default any kind of read will be sent as a GET request and any kind of write
39826  * will be sent as a POST request).
39827  *
39828  * # Limitations
39829  *
39830  * AjaxProxy cannot be used to retrieve data from other domains. If your application is running on http://domainA.com it
39831  * cannot load data from http://domainB.com because browsers have a built-in security policy that prohibits domains
39832  * talking to each other via AJAX.
39833  *
39834  * If you need to read data from another domain and can't set up a proxy server (some software that runs on your own
39835  * domain's web server and transparently forwards requests to http://domainB.com, making it look like they actually came
39836  * from http://domainA.com), you can use {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with
39837  * Padding), which can help you get around the problem so long as the server on http://domainB.com is set up to support
39838  * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.
39839  *
39840  * # Readers and Writers
39841  *
39842  * AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode the server's response.
39843  * If no Reader is supplied, AjaxProxy will default to using a {@link Ext.data.reader.Json JsonReader}. Reader
39844  * configuration can be passed in as a simple object, which the Proxy automatically turns into a {@link
39845  * Ext.data.reader.Reader Reader} instance:
39846  *
39847  *     var proxy = new Ext.data.proxy.Ajax({
39848  *         model: 'User',
39849  *         reader: {
39850  *             type: 'xml',
39851  *             root: 'users'
39852  *         }
39853  *     });
39854  *
39855  *     proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
39856  *
39857  * # Url generation
39858  *
39859  * AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url it generates for
39860  * each request. These are controlled with the following configuration options:
39861  *
39862  * - {@link #pageParam} - controls how the page number is sent to the server (see also {@link #startParam} and {@link #limitParam})
39863  * - {@link #sortParam} - controls how sort information is sent to the server
39864  * - {@link #groupParam} - controls how grouping information is sent to the server
39865  * - {@link #filterParam} - controls how filter information is sent to the server
39866  *
39867  * Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see how we can customize
39868  * the generated urls, let's say we're loading the Proxy with the following Operation:
39869  *
39870  *     var operation = new Ext.data.Operation({
39871  *         action: 'read',
39872  *         page  : 2
39873  *     });
39874  *
39875  * Now we'll issue the request for this Operation by calling {@link #read}:
39876  *
39877  *     var proxy = new Ext.data.proxy.Ajax({
39878  *         url: '/users'
39879  *     });
39880  *
39881  *     proxy.read(operation); //GET /users?page=2
39882  *
39883  * Easy enough - the Proxy just copied the page property from the Operation. We can customize how this page data is sent
39884  * to the server:
39885  *
39886  *     var proxy = new Ext.data.proxy.Ajax({
39887  *         url: '/users',
39888  *         pagePage: 'pageNumber'
39889  *     });
39890  *
39891  *     proxy.read(operation); //GET /users?pageNumber=2
39892  *
39893  * Alternatively, our Operation could have been configured to send start and limit parameters instead of page:
39894  *
39895  *     var operation = new Ext.data.Operation({
39896  *         action: 'read',
39897  *         start : 50,
39898  *         limit : 25
39899  *     });
39900  *
39901  *     var proxy = new Ext.data.proxy.Ajax({
39902  *         url: '/users'
39903  *     });
39904  *
39905  *     proxy.read(operation); //GET /users?start=50&limit;=25
39906  *
39907  * Again we can customize this url:
39908  *
39909  *     var proxy = new Ext.data.proxy.Ajax({
39910  *         url: '/users',
39911  *         startParam: 'startIndex',
39912  *         limitParam: 'limitIndex'
39913  *     });
39914  *
39915  *     proxy.read(operation); //GET /users?startIndex=50&limitIndex;=25
39916  *
39917  * AjaxProxy will also send sort and filter information to the server. Let's take a look at how this looks with a more
39918  * expressive Operation object:
39919  *
39920  *     var operation = new Ext.data.Operation({
39921  *         action: 'read',
39922  *         sorters: [
39923  *             new Ext.util.Sorter({
39924  *                 property : 'name',
39925  *                 direction: 'ASC'
39926  *             }),
39927  *             new Ext.util.Sorter({
39928  *                 property : 'age',
39929  *                 direction: 'DESC'
39930  *             })
39931  *         ],
39932  *         filters: [
39933  *             new Ext.util.Filter({
39934  *                 property: 'eyeColor',
39935  *                 value   : 'brown'
39936  *             })
39937  *         ]
39938  *     });
39939  *
39940  * This is the type of object that is generated internally when loading a {@link Ext.data.Store Store} with sorters and
39941  * filters defined. By default the AjaxProxy will JSON encode the sorters and filters, resulting in something like this
39942  * (note that the url is escaped before sending the request, but is left unescaped here for clarity):
39943  *
39944  *     var proxy = new Ext.data.proxy.Ajax({
39945  *         url: '/users'
39946  *     });
39947  *
39948  *     proxy.read(operation); //GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter;=[{"property":"eyeColor","value":"brown"}]
39949  *
39950  * We can again customize how this is created by supplying a few configuration options. Let's say our server is set up
39951  * to receive sorting information is a format like "sortBy=name#ASC,age#DESC". We can configure AjaxProxy to provide
39952  * that format like this:
39953  *
39954  *      var proxy = new Ext.data.proxy.Ajax({
39955  *          url: '/users',
39956  *          sortParam: 'sortBy',
39957  *          filterParam: 'filterBy',
39958  *
39959  *          //our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
39960  *          encodeSorters: function(sorters) {
39961  *              var length   = sorters.length,
39962  *                  sortStrs = [],
39963  *                  sorter, i;
39964  *
39965  *              for (i = 0; i < length; i++) {
39966  *                  sorter = sorters[i];
39967  *
39968  *                  sortStrs[i] = sorter.property + '#' + sorter.direction
39969  *              }
39970  *
39971  *              return sortStrs.join(",");
39972  *          }
39973  *      });
39974  *
39975  *      proxy.read(operation); //GET /users?sortBy=name#ASC,age#DESC&filterBy;=[{"property":"eyeColor","value":"brown"}]
39976  *
39977  * We can also provide a custom {@link #encodeFilters} function to encode our filters.
39978  *
39979  * @constructor
39980  * Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the Store's call to
39981  * {@link Ext.data.Store#load load} will override any specified callback and params options. In this case, use the
39982  * {@link Ext.data.Store Store}'s events to modify parameters, or react to loading events.
39983  *
39984  * @param {Object} config (optional) Config object.
39985  * If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make the request.
39986  */
39987 Ext.define('Ext.data.proxy.Ajax', {
39988     requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
39989     extend: 'Ext.data.proxy.Server',
39990     alias: 'proxy.ajax',
39991     alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
39992     
39993     /**
39994      * @property {Object} actionMethods
39995      * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to 'GET' for 'read' actions
39996      * and 'POST' for 'create', 'update' and 'destroy' actions. The {@link Ext.data.proxy.Rest} maps these to the
39997      * correct RESTful methods.
39998      */
39999     actionMethods: {
40000         create : 'POST',
40001         read   : 'GET',
40002         update : 'POST',
40003         destroy: 'POST'
40004     },
40005     
40006     /**
40007      * @cfg {Object} headers
40008      * Any headers to add to the Ajax request. Defaults to undefined.
40009      */
40010     
40011     /**
40012      * @ignore
40013      */
40014     doRequest: function(operation, callback, scope) {
40015         var writer  = this.getWriter(),
40016             request = this.buildRequest(operation, callback, scope);
40017             
40018         if (operation.allowWrite()) {
40019             request = writer.write(request);
40020         }
40021         
40022         Ext.apply(request, {
40023             headers       : this.headers,
40024             timeout       : this.timeout,
40025             scope         : this,
40026             callback      : this.createRequestCallback(request, operation, callback, scope),
40027             method        : this.getMethod(request),
40028             disableCaching: false // explicitly set it to false, ServerProxy handles caching
40029         });
40030         
40031         Ext.Ajax.request(request);
40032         
40033         return request;
40034     },
40035     
40036     /**
40037      * Returns the HTTP method name for a given request. By default this returns based on a lookup on
40038      * {@link #actionMethods}.
40039      * @param {Ext.data.Request} request The request object
40040      * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE')
40041      */
40042     getMethod: function(request) {
40043         return this.actionMethods[request.action];
40044     },
40045     
40046     /**
40047      * @private
40048      * TODO: This is currently identical to the JsonPProxy version except for the return function's signature. There is a lot
40049      * of code duplication inside the returned function so we need to find a way to DRY this up.
40050      * @param {Ext.data.Request} request The Request object
40051      * @param {Ext.data.Operation} operation The Operation being executed
40052      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
40053      * passed to doRequest
40054      * @param {Object} scope The scope in which to execute the callback function
40055      * @return {Function} The callback function
40056      */
40057     createRequestCallback: function(request, operation, callback, scope) {
40058         var me = this;
40059         
40060         return function(options, success, response) {
40061             me.processResponse(success, operation, request, response, callback, scope);
40062         };
40063     }
40064 }, function() {
40065     //backwards compatibility, remove in Ext JS 5.0
40066     Ext.data.HttpProxy = this;
40067 });
40068
40069 /**
40070  * @author Ed Spencer
40071  *
40072  * A Model represents some object that your application manages. For example, one might define a Model for Users,
40073  * Products, Cars, or any other real-world object that we want to model in the system. Models are registered via the
40074  * {@link Ext.ModelManager model manager}, and are used by {@link Ext.data.Store stores}, which are in turn used by many
40075  * of the data-bound components in Ext.
40076  *
40077  * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:
40078  *
40079  *     Ext.define('User', {
40080  *         extend: 'Ext.data.Model',
40081  *         fields: [
40082  *             {name: 'name',  type: 'string'},
40083  *             {name: 'age',   type: 'int'},
40084  *             {name: 'phone', type: 'string'},
40085  *             {name: 'alive', type: 'boolean', defaultValue: true}
40086  *         ],
40087  *
40088  *         changeName: function() {
40089  *             var oldName = this.get('name'),
40090  *                 newName = oldName + " The Barbarian";
40091  *
40092  *             this.set('name', newName);
40093  *         }
40094  *     });
40095  *
40096  * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link
40097  * Ext.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype.
40098  *
40099  * Now we can create instances of our User model and call any model logic we defined:
40100  *
40101  *     var user = Ext.create('User', {
40102  *         name : 'Conan',
40103  *         age  : 24,
40104  *         phone: '555-555-5555'
40105  *     });
40106  *
40107  *     user.changeName();
40108  *     user.get('name'); //returns "Conan The Barbarian"
40109  *
40110  * # Validations
40111  *
40112  * Models have built-in support for validations, which are executed against the validator functions in {@link
40113  * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to
40114  * models:
40115  *
40116  *     Ext.define('User', {
40117  *         extend: 'Ext.data.Model',
40118  *         fields: [
40119  *             {name: 'name',     type: 'string'},
40120  *             {name: 'age',      type: 'int'},
40121  *             {name: 'phone',    type: 'string'},
40122  *             {name: 'gender',   type: 'string'},
40123  *             {name: 'username', type: 'string'},
40124  *             {name: 'alive',    type: 'boolean', defaultValue: true}
40125  *         ],
40126  *
40127  *         validations: [
40128  *             {type: 'presence',  field: 'age'},
40129  *             {type: 'length',    field: 'name',     min: 2},
40130  *             {type: 'inclusion', field: 'gender',   list: ['Male', 'Female']},
40131  *             {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
40132  *             {type: 'format',    field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
40133  *         ]
40134  *     });
40135  *
40136  * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
40137  * object:
40138  *
40139  *     var instance = Ext.create('User', {
40140  *         name: 'Ed',
40141  *         gender: 'Male',
40142  *         username: 'edspencer'
40143  *     });
40144  *
40145  *     var errors = instance.validate();
40146  *
40147  * # Associations
40148  *
40149  * Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and {@link
40150  * Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration
40151  * application which deals with Users, Posts and Comments. We can express the relationships between these models like
40152  * this:
40153  *
40154  *     Ext.define('Post', {
40155  *         extend: 'Ext.data.Model',
40156  *         fields: ['id', 'user_id'],
40157  *
40158  *         belongsTo: 'User',
40159  *         hasMany  : {model: 'Comment', name: 'comments'}
40160  *     });
40161  *
40162  *     Ext.define('Comment', {
40163  *         extend: 'Ext.data.Model',
40164  *         fields: ['id', 'user_id', 'post_id'],
40165  *
40166  *         belongsTo: 'Post'
40167  *     });
40168  *
40169  *     Ext.define('User', {
40170  *         extend: 'Ext.data.Model',
40171  *         fields: ['id'],
40172  *
40173  *         hasMany: [
40174  *             'Post',
40175  *             {model: 'Comment', name: 'comments'}
40176  *         ]
40177  *     });
40178  *
40179  * See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the
40180  * usage and configuration of associations. Note that associations can also be specified like this:
40181  *
40182  *     Ext.define('User', {
40183  *         extend: 'Ext.data.Model',
40184  *         fields: ['id'],
40185  *
40186  *         associations: [
40187  *             {type: 'hasMany', model: 'Post',    name: 'posts'},
40188  *             {type: 'hasMany', model: 'Comment', name: 'comments'}
40189  *         ]
40190  *     });
40191  *
40192  * # Using a Proxy
40193  *
40194  * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
40195  * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
40196  * can be set directly on the Model:
40197  *
40198  *     Ext.define('User', {
40199  *         extend: 'Ext.data.Model',
40200  *         fields: ['id', 'name', 'email'],
40201  *
40202  *         proxy: {
40203  *             type: 'rest',
40204  *             url : '/users'
40205  *         }
40206  *     });
40207  *
40208  * 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
40209  * RESTful backend. Let's see how this works:
40210  *
40211  *     var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
40212  *
40213  *     user.save(); //POST /users
40214  *
40215  * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
40216  * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
40217  * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
40218  * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
40219  *
40220  * Loading data via the Proxy is equally easy:
40221  *
40222  *     //get a reference to the User model class
40223  *     var User = Ext.ModelManager.getModel('User');
40224  *
40225  *     //Uses the configured RestProxy to make a GET request to /users/123
40226  *     User.load(123, {
40227  *         success: function(user) {
40228  *             console.log(user.getId()); //logs 123
40229  *         }
40230  *     });
40231  *
40232  * Models can also be updated and destroyed easily:
40233  *
40234  *     //the user Model we loaded in the last snippet:
40235  *     user.set('name', 'Edward Spencer');
40236  *
40237  *     //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
40238  *     user.save({
40239  *         success: function() {
40240  *             console.log('The User was updated');
40241  *         }
40242  *     });
40243  *
40244  *     //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
40245  *     user.destroy({
40246  *         success: function() {
40247  *             console.log('The User was destroyed!');
40248  *         }
40249  *     });
40250  *
40251  * # Usage in Stores
40252  *
40253  * 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
40254  * creating a {@link Ext.data.Store Store}:
40255  *
40256  *     var store = Ext.create('Ext.data.Store', {
40257  *         model: 'User'
40258  *     });
40259  *
40260  *     //uses the Proxy we set up on Model to load the Store data
40261  *     store.load();
40262  *
40263  * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
40264  * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
40265  * Ext.data.Store Store docs} for more information on Stores.
40266  *
40267  * @constructor
40268  * Creates new Model instance.
40269  * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
40270  * @param {Number} id (optional) Unique ID to assign to this model instance
40271  */
40272 Ext.define('Ext.data.Model', {
40273     alternateClassName: 'Ext.data.Record',
40274
40275     mixins: {
40276         observable: 'Ext.util.Observable'
40277     },
40278
40279     requires: [
40280         'Ext.ModelManager',
40281         'Ext.data.IdGenerator',
40282         'Ext.data.Field',
40283         'Ext.data.Errors',
40284         'Ext.data.Operation',
40285         'Ext.data.validations',
40286         'Ext.data.proxy.Ajax',
40287         'Ext.util.MixedCollection'
40288     ],
40289
40290     onClassExtended: function(cls, data) {
40291         var onBeforeClassCreated = data.onBeforeClassCreated;
40292
40293         data.onBeforeClassCreated = function(cls, data) {
40294             var me = this,
40295                 name = Ext.getClassName(cls),
40296                 prototype = cls.prototype,
40297                 superCls = cls.prototype.superclass,
40298
40299                 validations = data.validations || [],
40300                 fields = data.fields || [],
40301                 associations = data.associations || [],
40302                 belongsTo = data.belongsTo,
40303                 hasMany = data.hasMany,
40304                 idgen = data.idgen,
40305
40306                 fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
40307                     return field.name;
40308                 }),
40309
40310                 associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
40311                     return association.name;
40312                 }),
40313
40314                 superValidations = superCls.validations,
40315                 superFields = superCls.fields,
40316                 superAssociations = superCls.associations,
40317
40318                 association, i, ln,
40319                 dependencies = [];
40320
40321             // Save modelName on class and its prototype
40322             cls.modelName = name;
40323             prototype.modelName = name;
40324
40325             // Merge the validations of the superclass and the new subclass
40326             if (superValidations) {
40327                 validations = superValidations.concat(validations);
40328             }
40329
40330             data.validations = validations;
40331
40332             // Merge the fields of the superclass and the new subclass
40333             if (superFields) {
40334                 fields = superFields.items.concat(fields);
40335             }
40336
40337             for (i = 0, ln = fields.length; i < ln; ++i) {
40338                 fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
40339             }
40340
40341             data.fields = fieldsMixedCollection;
40342
40343             if (idgen) {
40344                 data.idgen = Ext.data.IdGenerator.get(idgen);
40345             }
40346
40347             //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
40348             //we support that here
40349             if (belongsTo) {
40350                 belongsTo = Ext.Array.from(belongsTo);
40351
40352                 for (i = 0, ln = belongsTo.length; i < ln; ++i) {
40353                     association = belongsTo[i];
40354
40355                     if (!Ext.isObject(association)) {
40356                         association = {model: association};
40357                     }
40358
40359                     association.type = 'belongsTo';
40360                     associations.push(association);
40361                 }
40362
40363                 delete data.belongsTo;
40364             }
40365
40366             if (hasMany) {
40367                 hasMany = Ext.Array.from(hasMany);
40368                 for (i = 0, ln = hasMany.length; i < ln; ++i) {
40369                     association = hasMany[i];
40370
40371                     if (!Ext.isObject(association)) {
40372                         association = {model: association};
40373                     }
40374
40375                     association.type = 'hasMany';
40376                     associations.push(association);
40377                 }
40378
40379                 delete data.hasMany;
40380             }
40381
40382             if (superAssociations) {
40383                 associations = superAssociations.items.concat(associations);
40384             }
40385
40386             for (i = 0, ln = associations.length; i < ln; ++i) {
40387                 dependencies.push('association.' + associations[i].type.toLowerCase());
40388             }
40389
40390             if (data.proxy) {
40391                 if (typeof data.proxy === 'string') {
40392                     dependencies.push('proxy.' + data.proxy);
40393                 }
40394                 else if (typeof data.proxy.type === 'string') {
40395                     dependencies.push('proxy.' + data.proxy.type);
40396                 }
40397             }
40398
40399             Ext.require(dependencies, function() {
40400                 Ext.ModelManager.registerType(name, cls);
40401
40402                 for (i = 0, ln = associations.length; i < ln; ++i) {
40403                     association = associations[i];
40404
40405                     Ext.apply(association, {
40406                         ownerModel: name,
40407                         associatedModel: association.model
40408                     });
40409
40410                     if (Ext.ModelManager.getModel(association.model) === undefined) {
40411                         Ext.ModelManager.registerDeferredAssociation(association);
40412                     } else {
40413                         associationsMixedCollection.add(Ext.data.Association.create(association));
40414                     }
40415                 }
40416
40417                 data.associations = associationsMixedCollection;
40418
40419                 onBeforeClassCreated.call(me, cls, data);
40420
40421                 cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
40422
40423                 // Fire the onModelDefined template method on ModelManager
40424                 Ext.ModelManager.onModelDefined(cls);
40425             });
40426         };
40427     },
40428
40429     inheritableStatics: {
40430         /**
40431          * Sets the Proxy to use for this model. Accepts any options that can be accepted by
40432          * {@link Ext#createByAlias Ext.createByAlias}.
40433          * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
40434          * @return {Ext.data.proxy.Proxy}
40435          * @static
40436          * @inheritable
40437          */
40438         setProxy: function(proxy) {
40439             //make sure we have an Ext.data.proxy.Proxy object
40440             if (!proxy.isProxy) {
40441                 if (typeof proxy == "string") {
40442                     proxy = {
40443                         type: proxy
40444                     };
40445                 }
40446                 proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
40447             }
40448             proxy.setModel(this);
40449             this.proxy = this.prototype.proxy = proxy;
40450
40451             return proxy;
40452         },
40453
40454         /**
40455          * Returns the configured Proxy for this Model
40456          * @return {Ext.data.proxy.Proxy} The proxy
40457          * @static
40458          * @inheritable
40459          */
40460         getProxy: function() {
40461             return this.proxy;
40462         },
40463
40464         /**
40465          * Asynchronously loads a model instance by id. Sample usage:
40466          *
40467          *     MyApp.User = Ext.define('User', {
40468          *         extend: 'Ext.data.Model',
40469          *         fields: [
40470          *             {name: 'id', type: 'int'},
40471          *             {name: 'name', type: 'string'}
40472          *         ]
40473          *     });
40474          *
40475          *     MyApp.User.load(10, {
40476          *         scope: this,
40477          *         failure: function(record, operation) {
40478          *             //do something if the load failed
40479          *         },
40480          *         success: function(record, operation) {
40481          *             //do something if the load succeeded
40482          *         },
40483          *         callback: function(record, operation) {
40484          *             //do something whether the load succeeded or failed
40485          *         }
40486          *     });
40487          *
40488          * @param {Number} id The id of the model to load
40489          * @param {Object} config (optional) config object containing success, failure and callback functions, plus
40490          * optional scope
40491          * @static
40492          * @inheritable
40493          */
40494         load: function(id, config) {
40495             config = Ext.apply({}, config);
40496             config = Ext.applyIf(config, {
40497                 action: 'read',
40498                 id    : id
40499             });
40500
40501             var operation  = Ext.create('Ext.data.Operation', config),
40502                 scope      = config.scope || this,
40503                 record     = null,
40504                 callback;
40505
40506             callback = function(operation) {
40507                 if (operation.wasSuccessful()) {
40508                     record = operation.getRecords()[0];
40509                     Ext.callback(config.success, scope, [record, operation]);
40510                 } else {
40511                     Ext.callback(config.failure, scope, [record, operation]);
40512                 }
40513                 Ext.callback(config.callback, scope, [record, operation]);
40514             };
40515
40516             this.proxy.read(operation, callback, this);
40517         }
40518     },
40519
40520     statics: {
40521         PREFIX : 'ext-record',
40522         AUTO_ID: 1,
40523         EDIT   : 'edit',
40524         REJECT : 'reject',
40525         COMMIT : 'commit',
40526
40527         /**
40528          * Generates a sequential id. This method is typically called when a record is {@link Ext#create
40529          * create}d and {@link #constructor no id has been specified}. The id will automatically be assigned to the
40530          * record. The returned id takes the form: {PREFIX}-{AUTO_ID}.
40531          *
40532          * - **PREFIX** : String - Ext.data.Model.PREFIX (defaults to 'ext-record')
40533          * - **AUTO_ID** : String - Ext.data.Model.AUTO_ID (defaults to 1 initially)
40534          *
40535          * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}.
40536          * @return {String} auto-generated string id, `"ext-record-i++"`;
40537          * @static
40538          */
40539         id: function(rec) {
40540             var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
40541             rec.phantom = true;
40542             rec.internalId = id;
40543             return id;
40544         }
40545     },
40546
40547     /**
40548      * @cfg {String/Object} idgen
40549      * The id generator to use for this model. The default id generator does not generate
40550      * values for the {@link #idProperty}.
40551      *
40552      * This can be overridden at the model level to provide a custom generator for a model.
40553      * The simplest form of this would be:
40554      *
40555      *      Ext.define('MyApp.data.MyModel', {
40556      *          extend: 'Ext.data.Model',
40557      *          requires: ['Ext.data.SequentialIdGenerator'],
40558      *          idgen: 'sequential',
40559      *          ...
40560      *      });
40561      *
40562      * The above would generate {@link Ext.data.SequentialIdGenerator sequential} id's such
40563      * as 1, 2, 3 etc..
40564      *
40565      * Another useful id generator is {@link Ext.data.UuidGenerator}:
40566      *
40567      *      Ext.define('MyApp.data.MyModel', {
40568      *          extend: 'Ext.data.Model',
40569      *          requires: ['Ext.data.UuidGenerator'],
40570      *          idgen: 'uuid',
40571      *          ...
40572      *      });
40573      *
40574      * An id generation can also be further configured:
40575      *
40576      *      Ext.define('MyApp.data.MyModel', {
40577      *          extend: 'Ext.data.Model',
40578      *          idgen: {
40579      *              type: 'sequential',
40580      *              seed: 1000,
40581      *              prefix: 'ID_'
40582      *          }
40583      *      });
40584      *
40585      * The above would generate id's such as ID_1000, ID_1001, ID_1002 etc..
40586      *
40587      * If multiple models share an id space, a single generator can be shared:
40588      *
40589      *      Ext.define('MyApp.data.MyModelX', {
40590      *          extend: 'Ext.data.Model',
40591      *          idgen: {
40592      *              type: 'sequential',
40593      *              id: 'xy'
40594      *          }
40595      *      });
40596      *
40597      *      Ext.define('MyApp.data.MyModelY', {
40598      *          extend: 'Ext.data.Model',
40599      *          idgen: {
40600      *              type: 'sequential',
40601      *              id: 'xy'
40602      *          }
40603      *      });
40604      *
40605      * For more complex, shared id generators, a custom generator is the best approach.
40606      * See {@link Ext.data.IdGenerator} for details on creating custom id generators.
40607      *
40608      * @markdown
40609      */
40610     idgen: {
40611         isGenerator: true,
40612         type: 'default',
40613
40614         generate: function () {
40615             return null;
40616         },
40617         getRecId: function (rec) {
40618             return rec.modelName + '-' + rec.internalId;
40619         }
40620     },
40621
40622     /**
40623      * @property {Boolean} editing
40624      * Internal flag used to track whether or not the model instance is currently being edited. Read-only.
40625      */
40626     editing : false,
40627
40628     /**
40629      * @property {Boolean} dirty
40630      * True if this Record has been modified. Read-only.
40631      */
40632     dirty : false,
40633
40634     /**
40635      * @cfg {String} persistenceProperty
40636      * The property on this Persistable object that its data is saved to. Defaults to 'data'
40637      * (e.g. all persistable data resides in this.data.)
40638      */
40639     persistenceProperty: 'data',
40640
40641     evented: false,
40642     isModel: true,
40643
40644     /**
40645      * @property {Boolean} phantom
40646      * True when the record does not yet exist in a server-side database (see {@link #setDirty}).
40647      * Any record which has a real database pk set as its id property is NOT a phantom -- it's real.
40648      */
40649     phantom : false,
40650
40651     /**
40652      * @cfg {String} idProperty
40653      * The name of the field treated as this Model's unique id. Defaults to 'id'.
40654      */
40655     idProperty: 'id',
40656
40657     /**
40658      * @cfg {String} defaultProxyType
40659      * The string type of the default Model Proxy. Defaults to 'ajax'.
40660      */
40661     defaultProxyType: 'ajax',
40662
40663     // Fields config and property
40664     /**
40665      * @cfg {Object[]/String[]} fields
40666      * The fields for this model.
40667      */
40668     /**
40669      * @property {Ext.util.MixedCollection} fields
40670      * The fields defined on this model.
40671      */
40672
40673     /**
40674      * @cfg {Object[]} validations
40675      * An array of {@link Ext.data.validations validations} for this model.
40676      */
40677
40678     // Associations configs and properties
40679     /**
40680      * @cfg {Object[]} associations
40681      * An array of {@link Ext.data.Association associations} for this model.
40682      */
40683     /**
40684      * @cfg {String/Object/String[]/Object[]} hasMany
40685      * One or more {@link Ext.data.HasManyAssociation HasMany associations} for this model.
40686      */
40687     /**
40688      * @cfg {String/Object/String[]/Object[]} belongsTo
40689      * One or more {@link Ext.data.BelongsToAssociation BelongsTo associations} for this model.
40690      */
40691     /**
40692      * @property {Ext.util.MixedCollection} associations
40693      * {@link Ext.data.Association Associations} defined on this model.
40694      */
40695
40696     /**
40697      * @cfg {String/Object/Ext.data.proxy.Proxy} proxy
40698      * The {@link Ext.data.proxy.Proxy proxy} to use for this model.
40699      */
40700
40701     // raw not documented intentionally, meant to be used internally.
40702     constructor: function(data, id, raw) {
40703         data = data || {};
40704
40705         var me = this,
40706             fields,
40707             length,
40708             field,
40709             name,
40710             i,
40711             newId,
40712             isArray = Ext.isArray(data),
40713             newData = isArray ? {} : null; // to hold mapped array data if needed
40714
40715         /**
40716          * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
40717          * @property internalId
40718          * @type String
40719          * @private
40720          */
40721         me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
40722
40723         /**
40724          * @property {Object} raw The raw data used to create this model if created via a reader.
40725          */
40726         me.raw = raw;
40727
40728         Ext.applyIf(me, {
40729             data: {}
40730         });
40731
40732         /**
40733          * @property {Object} modified Key: value pairs of all fields whose values have changed
40734          */
40735         me.modified = {};
40736
40737         // Deal with spelling error in previous releases
40738         if (me.persistanceProperty) {
40739             me.persistenceProperty = me.persistanceProperty;
40740         }
40741         me[me.persistenceProperty] = {};
40742
40743         me.mixins.observable.constructor.call(me);
40744
40745         //add default field values if present
40746         fields = me.fields.items;
40747         length = fields.length;
40748
40749         for (i = 0; i < length; i++) {
40750             field = fields[i];
40751             name  = field.name;
40752
40753             if (isArray){
40754                 // Have to map array data so the values get assigned to the named fields
40755                 // rather than getting set as the field names with undefined values.
40756                 newData[name] = data[i];
40757             }
40758             else if (data[name] === undefined) {
40759                 data[name] = field.defaultValue;
40760             }
40761         }
40762
40763         me.set(newData || data);
40764
40765         if (me.getId()) {
40766             me.phantom = false;
40767         } else if (me.phantom) {
40768             newId = me.idgen.generate();
40769             if (newId !== null) {
40770                 me.setId(newId);
40771             }
40772         }
40773
40774         // clear any dirty/modified since we're initializing
40775         me.dirty = false;
40776         me.modified = {};
40777
40778         if (typeof me.init == 'function') {
40779             me.init();
40780         }
40781
40782         me.id = me.idgen.getRecId(me);
40783     },
40784
40785     /**
40786      * Returns the value of the given field
40787      * @param {String} fieldName The field to fetch the value for
40788      * @return {Object} The value
40789      */
40790     get: function(field) {
40791         return this[this.persistenceProperty][field];
40792     },
40793
40794     /**
40795      * Sets the given field to the given value, marks the instance as dirty
40796      * @param {String/Object} fieldName The field to set, or an object containing key/value pairs
40797      * @param {Object} value The value to set
40798      */
40799     set: function(fieldName, value) {
40800         var me = this,
40801             fields = me.fields,
40802             modified = me.modified,
40803             convertFields = [],
40804             field, key, i, currentValue, notEditing, count, length;
40805
40806         /*
40807          * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
40808          * set those last so that all other possible data is set before the convert function is called
40809          */
40810         if (arguments.length == 1 && Ext.isObject(fieldName)) {
40811             notEditing = !me.editing;
40812             count = 0;
40813             for (key in fieldName) {
40814                 if (fieldName.hasOwnProperty(key)) {
40815
40816                     //here we check for the custom convert function. Note that if a field doesn't have a convert function,
40817                     //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
40818                     field = fields.get(key);
40819                     if (field && field.convert !== field.type.convert) {
40820                         convertFields.push(key);
40821                         continue;
40822                     }
40823
40824                     if (!count && notEditing) {
40825                         me.beginEdit();
40826                     }
40827                     ++count;
40828                     me.set(key, fieldName[key]);
40829                 }
40830             }
40831
40832             length = convertFields.length;
40833             if (length) {
40834                 if (!count && notEditing) {
40835                     me.beginEdit();
40836                 }
40837                 count += length;
40838                 for (i = 0; i < length; i++) {
40839                     field = convertFields[i];
40840                     me.set(field, fieldName[field]);
40841                 }
40842             }
40843
40844             if (notEditing && count) {
40845                 me.endEdit();
40846             }
40847         } else {
40848             if (fields) {
40849                 field = fields.get(fieldName);
40850
40851                 if (field && field.convert) {
40852                     value = field.convert(value, me);
40853                 }
40854             }
40855             currentValue = me.get(fieldName);
40856             me[me.persistenceProperty][fieldName] = value;
40857
40858             if (field && field.persist && !me.isEqual(currentValue, value)) {
40859                 if (me.isModified(fieldName)) {
40860                     if (me.isEqual(modified[fieldName], value)) {
40861                         // the original value in me.modified equals the new value, so the
40862                         // field is no longer modified
40863                         delete modified[fieldName];
40864                         // we might have removed the last modified field, so check to see if
40865                         // there are any modified fields remaining and correct me.dirty:
40866                         me.dirty = false;
40867                         for (key in modified) {
40868                             if (modified.hasOwnProperty(key)){
40869                                 me.dirty = true;
40870                                 break;
40871                             }
40872                         }
40873                     }
40874                 } else {
40875                     me.dirty = true;
40876                     modified[fieldName] = currentValue;
40877                 }
40878             }
40879
40880             if (!me.editing) {
40881                 me.afterEdit();
40882             }
40883         }
40884     },
40885
40886     /**
40887      * Checks if two values are equal, taking into account certain
40888      * special factors, for example dates.
40889      * @private
40890      * @param {Object} a The first value
40891      * @param {Object} b The second value
40892      * @return {Boolean} True if the values are equal
40893      */
40894     isEqual: function(a, b){
40895         if (Ext.isDate(a) && Ext.isDate(b)) {
40896             return a.getTime() === b.getTime();
40897         }
40898         return a === b;
40899     },
40900
40901     /**
40902      * Begins an edit. While in edit mode, no events (e.g.. the `update` event) are relayed to the containing store.
40903      * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}.
40904      */
40905     beginEdit : function(){
40906         var me = this;
40907         if (!me.editing) {
40908             me.editing = true;
40909             me.dirtySave = me.dirty;
40910             me.dataSave = Ext.apply({}, me[me.persistenceProperty]);
40911             me.modifiedSave = Ext.apply({}, me.modified);
40912         }
40913     },
40914
40915     /**
40916      * Cancels all changes made in the current edit operation.
40917      */
40918     cancelEdit : function(){
40919         var me = this;
40920         if (me.editing) {
40921             me.editing = false;
40922             // reset the modified state, nothing changed since the edit began
40923             me.modified = me.modifiedSave;
40924             me[me.persistenceProperty] = me.dataSave;
40925             me.dirty = me.dirtySave;
40926             delete me.modifiedSave;
40927             delete me.dataSave;
40928             delete me.dirtySave;
40929         }
40930     },
40931
40932     /**
40933      * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will
40934      * fire).
40935      * @param {Boolean} silent True to not notify the store of the change
40936      */
40937     endEdit : function(silent){
40938         var me = this,
40939             didChange;
40940             
40941         if (me.editing) {
40942             me.editing = false;
40943             didChange = me.dirty || me.changedWhileEditing();
40944             delete me.modifiedSave;
40945             delete me.dataSave;
40946             delete me.dirtySave;
40947             if (silent !== true && didChange) {
40948                 me.afterEdit();
40949             }
40950         }
40951     },
40952     
40953     /**
40954      * Checks if the underlying data has changed during an edit. This doesn't necessarily
40955      * mean the record is dirty, however we still need to notify the store since it may need
40956      * to update any views.
40957      * @private
40958      * @return {Boolean} True if the underlying data has changed during an edit.
40959      */
40960     changedWhileEditing: function(){
40961         var me = this,
40962             saved = me.dataSave,
40963             data = me[me.persistenceProperty],
40964             key;
40965             
40966         for (key in data) {
40967             if (data.hasOwnProperty(key)) {
40968                 if (!me.isEqual(data[key], saved[key])) {
40969                     return true;
40970                 }
40971             }
40972         }
40973         return false; 
40974     },
40975
40976     /**
40977      * Gets a hash of only the fields that have been modified since this Model was created or commited.
40978      * @return {Object}
40979      */
40980     getChanges : function(){
40981         var modified = this.modified,
40982             changes  = {},
40983             field;
40984
40985         for (field in modified) {
40986             if (modified.hasOwnProperty(field)){
40987                 changes[field] = this.get(field);
40988             }
40989         }
40990
40991         return changes;
40992     },
40993
40994     /**
40995      * Returns true if the passed field name has been `{@link #modified}` since the load or last commit.
40996      * @param {String} fieldName {@link Ext.data.Field#name}
40997      * @return {Boolean}
40998      */
40999     isModified : function(fieldName) {
41000         return this.modified.hasOwnProperty(fieldName);
41001     },
41002
41003     /**
41004      * Marks this **Record** as `{@link #dirty}`. This method is used interally when adding `{@link #phantom}` records
41005      * to a {@link Ext.data.proxy.Server#writer writer enabled store}.
41006      *
41007      * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords}
41008      * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations.
41009      */
41010     setDirty : function() {
41011         var me = this,
41012             name;
41013
41014         me.dirty = true;
41015
41016         me.fields.each(function(field) {
41017             if (field.persist) {
41018                 name = field.name;
41019                 me.modified[name] = me.get(name);
41020             }
41021         }, me);
41022     },
41023
41024
41025     /**
41026      * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
41027      * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
41028      * reverted to their original values.
41029      *
41030      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of reject
41031      * operations.
41032      *
41033      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
41034      * Defaults to false.
41035      */
41036     reject : function(silent) {
41037         var me = this,
41038             modified = me.modified,
41039             field;
41040
41041         for (field in modified) {
41042             if (modified.hasOwnProperty(field)) {
41043                 if (typeof modified[field] != "function") {
41044                     me[me.persistenceProperty][field] = modified[field];
41045                 }
41046             }
41047         }
41048
41049         me.dirty = false;
41050         me.editing = false;
41051         me.modified = {};
41052
41053         if (silent !== true) {
41054             me.afterReject();
41055         }
41056     },
41057
41058     /**
41059      * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
41060      * instance since either creation or the last commit operation.
41061      *
41062      * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of commit
41063      * operations.
41064      *
41065      * @param {Boolean} silent (optional) True to skip notification of the owning store of the change.
41066      * Defaults to false.
41067      */
41068     commit : function(silent) {
41069         var me = this;
41070
41071         me.phantom = me.dirty = me.editing = false;
41072         me.modified = {};
41073
41074         if (silent !== true) {
41075             me.afterCommit();
41076         }
41077     },
41078
41079     /**
41080      * Creates a copy (clone) of this Model instance.
41081      *
41082      * @param {String} [id] A new id, defaults to the id of the instance being copied.
41083      * See `{@link Ext.data.Model#id id}`. To generate a phantom instance with a new id use:
41084      *
41085      *     var rec = record.copy(); // clone the record
41086      *     Ext.data.Model.id(rec); // automatically generate a unique sequential id
41087      *
41088      * @return {Ext.data.Model}
41089      */
41090     copy : function(newId) {
41091         var me = this;
41092
41093         return new me.self(Ext.apply({}, me[me.persistenceProperty]), newId || me.internalId);
41094     },
41095
41096     /**
41097      * Sets the Proxy to use for this model. Accepts any options that can be accepted by
41098      * {@link Ext#createByAlias Ext.createByAlias}.
41099      *
41100      * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
41101      * @return {Ext.data.proxy.Proxy}
41102      */
41103     setProxy: function(proxy) {
41104         //make sure we have an Ext.data.proxy.Proxy object
41105         if (!proxy.isProxy) {
41106             if (typeof proxy === "string") {
41107                 proxy = {
41108                     type: proxy
41109                 };
41110             }
41111             proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
41112         }
41113         proxy.setModel(this.self);
41114         this.proxy = proxy;
41115
41116         return proxy;
41117     },
41118
41119     /**
41120      * Returns the configured Proxy for this Model.
41121      * @return {Ext.data.proxy.Proxy} The proxy
41122      */
41123     getProxy: function() {
41124         return this.proxy;
41125     },
41126
41127     /**
41128      * Validates the current data against all of its configured {@link #validations}.
41129      * @return {Ext.data.Errors} The errors object
41130      */
41131     validate: function() {
41132         var errors      = Ext.create('Ext.data.Errors'),
41133             validations = this.validations,
41134             validators  = Ext.data.validations,
41135             length, validation, field, valid, type, i;
41136
41137         if (validations) {
41138             length = validations.length;
41139
41140             for (i = 0; i < length; i++) {
41141                 validation = validations[i];
41142                 field = validation.field || validation.name;
41143                 type  = validation.type;
41144                 valid = validators[type](validation, this.get(field));
41145
41146                 if (!valid) {
41147                     errors.add({
41148                         field  : field,
41149                         message: validation.message || validators[type + 'Message']
41150                     });
41151                 }
41152             }
41153         }
41154
41155         return errors;
41156     },
41157
41158     /**
41159      * Checks if the model is valid. See {@link #validate}.
41160      * @return {Boolean} True if the model is valid.
41161      */
41162     isValid: function(){
41163         return this.validate().isValid();
41164     },
41165
41166     /**
41167      * Saves the model instance using the configured proxy.
41168      * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
41169      * @return {Ext.data.Model} The Model instance
41170      */
41171     save: function(options) {
41172         options = Ext.apply({}, options);
41173
41174         var me     = this,
41175             action = me.phantom ? 'create' : 'update',
41176             record = null,
41177             scope  = options.scope || me,
41178             operation,
41179             callback;
41180
41181         Ext.apply(options, {
41182             records: [me],
41183             action : action
41184         });
41185
41186         operation = Ext.create('Ext.data.Operation', options);
41187
41188         callback = function(operation) {
41189             if (operation.wasSuccessful()) {
41190                 record = operation.getRecords()[0];
41191                 //we need to make sure we've set the updated data here. Ideally this will be redundant once the
41192                 //ModelCache is in place
41193                 me.set(record.data);
41194                 record.dirty = false;
41195
41196                 Ext.callback(options.success, scope, [record, operation]);
41197             } else {
41198                 Ext.callback(options.failure, scope, [record, operation]);
41199             }
41200
41201             Ext.callback(options.callback, scope, [record, operation]);
41202         };
41203
41204         me.getProxy()[action](operation, callback, me);
41205
41206         return me;
41207     },
41208
41209     /**
41210      * Destroys the model using the configured proxy.
41211      * @param {Object} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
41212      * @return {Ext.data.Model} The Model instance
41213      */
41214     destroy: function(options){
41215         options = Ext.apply({}, options);
41216
41217         var me     = this,
41218             record = null,
41219             scope  = options.scope || me,
41220             operation,
41221             callback;
41222
41223         Ext.apply(options, {
41224             records: [me],
41225             action : 'destroy'
41226         });
41227
41228         operation = Ext.create('Ext.data.Operation', options);
41229         callback = function(operation) {
41230             if (operation.wasSuccessful()) {
41231                 Ext.callback(options.success, scope, [record, operation]);
41232             } else {
41233                 Ext.callback(options.failure, scope, [record, operation]);
41234             }
41235             Ext.callback(options.callback, scope, [record, operation]);
41236         };
41237
41238         me.getProxy().destroy(operation, callback, me);
41239         return me;
41240     },
41241
41242     /**
41243      * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
41244      * @return {Number} The id
41245      */
41246     getId: function() {
41247         return this.get(this.idProperty);
41248     },
41249
41250     /**
41251      * Sets the model instance's id field to the given id.
41252      * @param {Number} id The new id
41253      */
41254     setId: function(id) {
41255         this.set(this.idProperty, id);
41256     },
41257
41258     /**
41259      * Tells this model instance that it has been added to a store.
41260      * @param {Ext.data.Store} store The store to which this model has been added.
41261      */
41262     join : function(store) {
41263         /**
41264          * @property {Ext.data.Store} store
41265          * The {@link Ext.data.Store Store} to which this Record belongs.
41266          */
41267         this.store = store;
41268     },
41269
41270     /**
41271      * Tells this model instance that it has been removed from the store.
41272      * @param {Ext.data.Store} store The store from which this model has been removed.
41273      */
41274     unjoin: function(store) {
41275         delete this.store;
41276     },
41277
41278     /**
41279      * @private
41280      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41281      * afterEdit method is called
41282      */
41283     afterEdit : function() {
41284         this.callStore('afterEdit');
41285     },
41286
41287     /**
41288      * @private
41289      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41290      * afterReject method is called
41291      */
41292     afterReject : function() {
41293         this.callStore("afterReject");
41294     },
41295
41296     /**
41297      * @private
41298      * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
41299      * afterCommit method is called
41300      */
41301     afterCommit: function() {
41302         this.callStore('afterCommit');
41303     },
41304
41305     /**
41306      * @private
41307      * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
41308      * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
41309      * will always be called with the model instance as its single argument.
41310      * @param {String} fn The function to call on the store
41311      */
41312     callStore: function(fn) {
41313         var store = this.store;
41314
41315         if (store !== undefined && typeof store[fn] == "function") {
41316             store[fn](this);
41317         }
41318     },
41319
41320     /**
41321      * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a
41322      * User which hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
41323      *
41324      *     {
41325      *         orders: [
41326      *             {
41327      *                 id: 123,
41328      *                 status: 'shipped',
41329      *                 orderItems: [
41330      *                     ...
41331      *                 ]
41332      *             }
41333      *         ]
41334      *     }
41335      *
41336      * @return {Object} The nested data set for the Model's loaded associations
41337      */
41338     getAssociatedData: function(){
41339         return this.prepareAssociatedData(this, [], null);
41340     },
41341
41342     /**
41343      * @private
41344      * This complex-looking method takes a given Model instance and returns an object containing all data from
41345      * all of that Model's *loaded* associations. See (@link #getAssociatedData}
41346      * @param {Ext.data.Model} record The Model instance
41347      * @param {String[]} ids PRIVATE. The set of Model instance internalIds that have already been loaded
41348      * @param {String} associationType (optional) The name of the type of association to limit to.
41349      * @return {Object} The nested data set for the Model's loaded associations
41350      */
41351     prepareAssociatedData: function(record, ids, associationType) {
41352         //we keep track of all of the internalIds of the models that we have loaded so far in here
41353         var associations     = record.associations.items,
41354             associationCount = associations.length,
41355             associationData  = {},
41356             associatedStore, associatedName, associatedRecords, associatedRecord,
41357             associatedRecordCount, association, id, i, j, type, allow;
41358
41359         for (i = 0; i < associationCount; i++) {
41360             association = associations[i];
41361             type = association.type;
41362             allow = true;
41363             if (associationType) {
41364                 allow = type == associationType;
41365             }
41366             if (allow && type == 'hasMany') {
41367
41368                 //this is the hasMany store filled with the associated data
41369                 associatedStore = record[association.storeName];
41370
41371                 //we will use this to contain each associated record's data
41372                 associationData[association.name] = [];
41373
41374                 //if it's loaded, put it into the association data
41375                 if (associatedStore && associatedStore.data.length > 0) {
41376                     associatedRecords = associatedStore.data.items;
41377                     associatedRecordCount = associatedRecords.length;
41378
41379                     //now we're finally iterating over the records in the association. We do this recursively
41380                     for (j = 0; j < associatedRecordCount; j++) {
41381                         associatedRecord = associatedRecords[j];
41382                         // Use the id, since it is prefixed with the model name, guaranteed to be unique
41383                         id = associatedRecord.id;
41384
41385                         //when we load the associations for a specific model instance we add it to the set of loaded ids so that
41386                         //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
41387                         if (Ext.Array.indexOf(ids, id) == -1) {
41388                             ids.push(id);
41389
41390                             associationData[association.name][j] = associatedRecord.data;
41391                             Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
41392                         }
41393                     }
41394                 }
41395             } else if (allow && type == 'belongsTo') {
41396                 associatedRecord = record[association.instanceName];
41397                 if (associatedRecord !== undefined) {
41398                     id = associatedRecord.id;
41399                     if (Ext.Array.indexOf(ids, id) == -1) {
41400                         ids.push(id);
41401                         associationData[association.name] = associatedRecord.data;
41402                         Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
41403                     }
41404                 }
41405             }
41406         }
41407
41408         return associationData;
41409     }
41410 });
41411
41412 /**
41413  * @docauthor Evan Trimboli <evan@sencha.com>
41414  *
41415  * Contains a collection of all stores that are created that have an identifier. An identifier can be assigned by
41416  * setting the {@link Ext.data.AbstractStore#storeId storeId} property. When a store is in the StoreManager, it can be
41417  * referred to via it's identifier:
41418  *
41419  *     Ext.create('Ext.data.Store', {
41420  *         model: 'SomeModel',
41421  *         storeId: 'myStore'
41422  *     });
41423  *
41424  *     var store = Ext.data.StoreManager.lookup('myStore');
41425  *
41426  * Also note that the {@link #lookup} method is aliased to {@link Ext#getStore} for convenience.
41427  *
41428  * If a store is registered with the StoreManager, you can also refer to the store by it's identifier when registering
41429  * it with any Component that consumes data from a store:
41430  *
41431  *     Ext.create('Ext.data.Store', {
41432  *         model: 'SomeModel',
41433  *         storeId: 'myStore'
41434  *     });
41435  *
41436  *     Ext.create('Ext.view.View', {
41437  *         store: 'myStore',
41438  *         // other configuration here
41439  *     });
41440  *
41441  */
41442 Ext.define('Ext.data.StoreManager', {
41443     extend: 'Ext.util.MixedCollection',
41444     alternateClassName: ['Ext.StoreMgr', 'Ext.data.StoreMgr', 'Ext.StoreManager'],
41445     singleton: true,
41446     uses: ['Ext.data.ArrayStore'],
41447     
41448     /**
41449      * @cfg {Object} listeners @hide
41450      */
41451
41452     /**
41453      * Registers one or more Stores with the StoreManager. You do not normally need to register stores manually. Any
41454      * store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
41455      * @param {Ext.data.Store...} stores Any number of Store instances
41456      */
41457     register : function() {
41458         for (var i = 0, s; (s = arguments[i]); i++) {
41459             this.add(s);
41460         }
41461     },
41462
41463     /**
41464      * Unregisters one or more Stores with the StoreManager
41465      * @param {String/Object...} stores Any number of Store instances or ID-s
41466      */
41467     unregister : function() {
41468         for (var i = 0, s; (s = arguments[i]); i++) {
41469             this.remove(this.lookup(s));
41470         }
41471     },
41472
41473     /**
41474      * Gets a registered Store by id
41475      * @param {String/Object} store The id of the Store, or a Store instance, or a store configuration
41476      * @return {Ext.data.Store}
41477      */
41478     lookup : function(store) {
41479         // handle the case when we are given an array or an array of arrays.
41480         if (Ext.isArray(store)) {
41481             var fields = ['field1'], 
41482                 expand = !Ext.isArray(store[0]),
41483                 data = store,
41484                 i,
41485                 len;
41486                 
41487             if(expand){
41488                 data = [];
41489                 for (i = 0, len = store.length; i < len; ++i) {
41490                     data.push([store[i]]);
41491                 }
41492             } else {
41493                 for(i = 2, len = store[0].length; i <= len; ++i){
41494                     fields.push('field' + i);
41495                 }
41496             }
41497             return Ext.create('Ext.data.ArrayStore', {
41498                 data  : data,
41499                 fields: fields,
41500                 autoDestroy: true,
41501                 autoCreated: true,
41502                 expanded: expand
41503             });
41504         }
41505         
41506         if (Ext.isString(store)) {
41507             // store id
41508             return this.get(store);
41509         } else {
41510             // store instance or store config
41511             return Ext.data.AbstractStore.create(store);
41512         }
41513     },
41514
41515     // getKey implementation for MixedCollection
41516     getKey : function(o) {
41517          return o.storeId;
41518     }
41519 }, function() {    
41520     /**
41521      * Creates a new store for the given id and config, then registers it with the {@link Ext.data.StoreManager Store Mananger}. 
41522      * Sample usage:
41523      *
41524      *     Ext.regStore('AllUsers', {
41525      *         model: 'User'
41526      *     });
41527      *
41528      *     // the store can now easily be used throughout the application
41529      *     new Ext.List({
41530      *         store: 'AllUsers',
41531      *         ... other config
41532      *     });
41533      *
41534      * @param {String} id The id to set on the new store
41535      * @param {Object} config The store config
41536      * @member Ext
41537      * @method regStore
41538      */
41539     Ext.regStore = function(name, config) {
41540         var store;
41541
41542         if (Ext.isObject(name)) {
41543             config = name;
41544         } else {
41545             config.storeId = name;
41546         }
41547
41548         if (config instanceof Ext.data.Store) {
41549             store = config;
41550         } else {
41551             store = Ext.create('Ext.data.Store', config);
41552         }
41553
41554         return Ext.data.StoreManager.register(store);
41555     };
41556
41557     /**
41558      * Shortcut to {@link Ext.data.StoreManager#lookup}.
41559      * @member Ext
41560      * @method getStore
41561      * @alias Ext.data.StoreManager#lookup
41562      */
41563     Ext.getStore = function(name) {
41564         return Ext.data.StoreManager.lookup(name);
41565     };
41566 });
41567
41568 /**
41569  * Base class for all Ext components. All subclasses of Component may participate in the automated Ext component
41570  * lifecycle of creation, rendering and destruction which is provided by the {@link Ext.container.Container Container}
41571  * class. Components may be added to a Container through the {@link Ext.container.Container#items items} config option
41572  * at the time the Container is created, or they may be added dynamically via the
41573  * {@link Ext.container.Container#add add} method.
41574  *
41575  * The Component base class has built-in support for basic hide/show and enable/disable and size control behavior.
41576  *
41577  * All Components are registered with the {@link Ext.ComponentManager} on construction so that they can be referenced at
41578  * any time via {@link Ext#getCmp Ext.getCmp}, passing the {@link #id}.
41579  *
41580  * All user-developed visual widgets that are required to participate in automated lifecycle and size management should
41581  * subclass Component.
41582  *
41583  * See the [Creating new UI controls][1] tutorial for details on how and to either extend or augment ExtJs base classes
41584  * to create custom Components.
41585  *
41586  * Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the xtype
41587  * like {@link #getXType} and {@link #isXType}. See the [Component Guide][2] for more information on xtypes and the
41588  * Component hierarchy.
41589  *
41590  * This is the list of all valid xtypes:
41591  *
41592  *     xtype            Class
41593  *     -------------    ------------------
41594  *     button           {@link Ext.button.Button}
41595  *     buttongroup      {@link Ext.container.ButtonGroup}
41596  *     colorpalette     {@link Ext.picker.Color}
41597  *     component        {@link Ext.Component}
41598  *     container        {@link Ext.container.Container}
41599  *     cycle            {@link Ext.button.Cycle}
41600  *     dataview         {@link Ext.view.View}
41601  *     datepicker       {@link Ext.picker.Date}
41602  *     editor           {@link Ext.Editor}
41603  *     editorgrid       {@link Ext.grid.plugin.Editing}
41604  *     grid             {@link Ext.grid.Panel}
41605  *     multislider      {@link Ext.slider.Multi}
41606  *     panel            {@link Ext.panel.Panel}
41607  *     progressbar      {@link Ext.ProgressBar}
41608  *     slider           {@link Ext.slider.Single}
41609  *     splitbutton      {@link Ext.button.Split}
41610  *     tabpanel         {@link Ext.tab.Panel}
41611  *     treepanel        {@link Ext.tree.Panel}
41612  *     viewport         {@link Ext.container.Viewport}
41613  *     window           {@link Ext.window.Window}
41614  *
41615  *     Toolbar components
41616  *     ---------------------------------------
41617  *     pagingtoolbar    {@link Ext.toolbar.Paging}
41618  *     toolbar          {@link Ext.toolbar.Toolbar}
41619  *     tbfill           {@link Ext.toolbar.Fill}
41620  *     tbitem           {@link Ext.toolbar.Item}
41621  *     tbseparator      {@link Ext.toolbar.Separator}
41622  *     tbspacer         {@link Ext.toolbar.Spacer}
41623  *     tbtext           {@link Ext.toolbar.TextItem}
41624  *
41625  *     Menu components
41626  *     ---------------------------------------
41627  *     menu             {@link Ext.menu.Menu}
41628  *     menucheckitem    {@link Ext.menu.CheckItem}
41629  *     menuitem         {@link Ext.menu.Item}
41630  *     menuseparator    {@link Ext.menu.Separator}
41631  *     menutextitem     {@link Ext.menu.Item}
41632  *
41633  *     Form components
41634  *     ---------------------------------------
41635  *     form             {@link Ext.form.Panel}
41636  *     checkbox         {@link Ext.form.field.Checkbox}
41637  *     combo            {@link Ext.form.field.ComboBox}
41638  *     datefield        {@link Ext.form.field.Date}
41639  *     displayfield     {@link Ext.form.field.Display}
41640  *     field            {@link Ext.form.field.Base}
41641  *     fieldset         {@link Ext.form.FieldSet}
41642  *     hidden           {@link Ext.form.field.Hidden}
41643  *     htmleditor       {@link Ext.form.field.HtmlEditor}
41644  *     label            {@link Ext.form.Label}
41645  *     numberfield      {@link Ext.form.field.Number}
41646  *     radio            {@link Ext.form.field.Radio}
41647  *     radiogroup       {@link Ext.form.RadioGroup}
41648  *     textarea         {@link Ext.form.field.TextArea}
41649  *     textfield        {@link Ext.form.field.Text}
41650  *     timefield        {@link Ext.form.field.Time}
41651  *     trigger          {@link Ext.form.field.Trigger}
41652  *
41653  *     Chart components
41654  *     ---------------------------------------
41655  *     chart            {@link Ext.chart.Chart}
41656  *     barchart         {@link Ext.chart.series.Bar}
41657  *     columnchart      {@link Ext.chart.series.Column}
41658  *     linechart        {@link Ext.chart.series.Line}
41659  *     piechart         {@link Ext.chart.series.Pie}
41660  *
41661  * It should not usually be necessary to instantiate a Component because there are provided subclasses which implement
41662  * specialized Component use cases which cover most application needs. However it is possible to instantiate a base
41663  * Component, and it will be renderable, or will particpate in layouts as the child item of a Container:
41664  *
41665  *     @example
41666  *     Ext.create('Ext.Component', {
41667  *         html: 'Hello world!',
41668  *         width: 300,
41669  *         height: 200,
41670  *         padding: 20,
41671  *         style: {
41672  *             color: '#FFFFFF',
41673  *             backgroundColor:'#000000'
41674  *         },
41675  *         renderTo: Ext.getBody()
41676  *     });
41677  *
41678  * The Component above creates its encapsulating `div` upon render, and use the configured HTML as content. More complex
41679  * internal structure may be created using the {@link #renderTpl} configuration, although to display database-derived
41680  * mass data, it is recommended that an ExtJS data-backed Component such as a {@link Ext.view.View View}, or {@link
41681  * Ext.grid.Panel GridPanel}, or {@link Ext.tree.Panel TreePanel} be used.
41682  *
41683  * [1]: http://sencha.com/learn/Tutorial:Creating_new_UI_controls
41684  */
41685 Ext.define('Ext.Component', {
41686
41687     /* Begin Definitions */
41688
41689     alias: ['widget.component', 'widget.box'],
41690
41691     extend: 'Ext.AbstractComponent',
41692
41693     requires: [
41694         'Ext.util.DelayedTask'
41695     ],
41696
41697     uses: [
41698         'Ext.Layer',
41699         'Ext.resizer.Resizer',
41700         'Ext.util.ComponentDragger'
41701     ],
41702
41703     mixins: {
41704         floating: 'Ext.util.Floating'
41705     },
41706
41707     statics: {
41708         // Collapse/expand directions
41709         DIRECTION_TOP: 'top',
41710         DIRECTION_RIGHT: 'right',
41711         DIRECTION_BOTTOM: 'bottom',
41712         DIRECTION_LEFT: 'left',
41713
41714         VERTICAL_DIRECTION_Re: /^(?:top|bottom)$/,
41715
41716         // RegExp whih specifies characters in an xtype which must be translated to '-' when generating auto IDs.
41717         // This includes dot, comma and whitespace
41718         INVALID_ID_CHARS_Re: /[\.,\s]/g
41719     },
41720
41721     /* End Definitions */
41722
41723     /**
41724      * @cfg {Boolean/Object} resizable
41725      * Specify as `true` to apply a {@link Ext.resizer.Resizer Resizer} to this Component after rendering.
41726      *
41727      * May also be specified as a config object to be passed to the constructor of {@link Ext.resizer.Resizer Resizer}
41728      * to override any defaults. By default the Component passes its minimum and maximum size, and uses
41729      * `{@link Ext.resizer.Resizer#dynamic}: false`
41730      */
41731
41732     /**
41733      * @cfg {String} resizeHandles
41734      * A valid {@link Ext.resizer.Resizer} handles config string. Only applies when resizable = true.
41735      */
41736     resizeHandles: 'all',
41737
41738     /**
41739      * @cfg {Boolean} [autoScroll=false]
41740      * `true` to use overflow:'auto' on the components layout element and show scroll bars automatically when necessary,
41741      * `false` to clip any overflowing content.
41742      */
41743
41744     /**
41745      * @cfg {Boolean} floating
41746      * Specify as true to float the Component outside of the document flow using CSS absolute positioning.
41747      *
41748      * Components such as {@link Ext.window.Window Window}s and {@link Ext.menu.Menu Menu}s are floating by default.
41749      *
41750      * Floating Components that are programatically {@link Ext.Component#render rendered} will register themselves with
41751      * the global {@link Ext.WindowManager ZIndexManager}
41752      *
41753      * ### Floating Components as child items of a Container
41754      *
41755      * A floating Component may be used as a child item of a Container. This just allows the floating Component to seek
41756      * a ZIndexManager by examining the ownerCt chain.
41757      *
41758      * When configured as floating, Components acquire, at render time, a {@link Ext.ZIndexManager ZIndexManager} which
41759      * manages a stack of related floating Components. The ZIndexManager brings a single floating Component to the top
41760      * of its stack when the Component's {@link #toFront} method is called.
41761      *
41762      * The ZIndexManager is found by traversing up the {@link #ownerCt} chain to find an ancestor which itself is
41763      * floating. This is so that descendant floating Components of floating _Containers_ (Such as a ComboBox dropdown
41764      * within a Window) can have its zIndex managed relative to any siblings, but always **above** that floating
41765      * ancestor Container.
41766      *
41767      * If no floating ancestor is found, a floating Component registers itself with the default {@link Ext.WindowManager
41768      * ZIndexManager}.
41769      *
41770      * Floating components _do not participate in the Container's layout_. Because of this, they are not rendered until
41771      * you explicitly {@link #show} them.
41772      *
41773      * After rendering, the ownerCt reference is deleted, and the {@link #floatParent} property is set to the found
41774      * floating ancestor Container. If no floating ancestor Container was found the {@link #floatParent} property will
41775      * not be set.
41776      */
41777     floating: false,
41778
41779     /**
41780      * @cfg {Boolean} toFrontOnShow
41781      * True to automatically call {@link #toFront} when the {@link #show} method is called on an already visible,
41782      * floating component.
41783      */
41784     toFrontOnShow: true,
41785
41786     /**
41787      * @property {Ext.ZIndexManager} zIndexManager
41788      * Only present for {@link #floating} Components after they have been rendered.
41789      *
41790      * A reference to the ZIndexManager which is managing this Component's z-index.
41791      *
41792      * The {@link Ext.ZIndexManager ZIndexManager} maintains a stack of floating Component z-indices, and also provides
41793      * a single modal mask which is insert just beneath the topmost visible modal floating Component.
41794      *
41795      * Floating Components may be {@link #toFront brought to the front} or {@link #toBack sent to the back} of the
41796      * z-index stack.
41797      *
41798      * This defaults to the global {@link Ext.WindowManager ZIndexManager} for floating Components that are
41799      * programatically {@link Ext.Component#render rendered}.
41800      *
41801      * For {@link #floating} Components which are added to a Container, the ZIndexManager is acquired from the first
41802      * ancestor Container found which is floating, or if not found the global {@link Ext.WindowManager ZIndexManager} is
41803      * used.
41804      *
41805      * See {@link #floating} and {@link #floatParent}
41806      */
41807
41808     /**
41809      * @property {Ext.Container} floatParent
41810      * Only present for {@link #floating} Components which were inserted as descendant items of floating Containers.
41811      *
41812      * Floating Components that are programatically {@link Ext.Component#render rendered} will not have a `floatParent`
41813      * property.
41814      *
41815      * For {@link #floating} Components which are child items of a Container, the floatParent will be the floating
41816      * ancestor Container which is responsible for the base z-index value of all its floating descendants. It provides
41817      * a {@link Ext.ZIndexManager ZIndexManager} which provides z-indexing services for all its descendant floating
41818      * Components.
41819      *
41820      * For example, the dropdown {@link Ext.view.BoundList BoundList} of a ComboBox which is in a Window will have the
41821      * Window as its `floatParent`
41822      *
41823      * See {@link #floating} and {@link #zIndexManager}
41824      */
41825
41826     /**
41827      * @cfg {Boolean/Object} [draggable=false]
41828      * Specify as true to make a {@link #floating} Component draggable using the Component's encapsulating element as
41829      * the drag handle.
41830      *
41831      * This may also be specified as a config object for the {@link Ext.util.ComponentDragger ComponentDragger} which is
41832      * instantiated to perform dragging.
41833      *
41834      * For example to create a Component which may only be dragged around using a certain internal element as the drag
41835      * handle, use the delegate option:
41836      *
41837      *     new Ext.Component({
41838      *         constrain: true,
41839      *         floating: true,
41840      *         style: {
41841      *             backgroundColor: '#fff',
41842      *             border: '1px solid black'
41843      *         },
41844      *         html: '<h1 style="cursor:move">The title</h1><p>The content</p>',
41845      *         draggable: {
41846      *             delegate: 'h1'
41847      *         }
41848      *     }).show();
41849      */
41850
41851     /**
41852      * @cfg {Boolean} [maintainFlex=false]
41853      * **Only valid when a sibling element of a {@link Ext.resizer.Splitter Splitter} within a
41854      * {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox} layout.**
41855      *
41856      * Specifies that if an immediate sibling Splitter is moved, the Component on the *other* side is resized, and this
41857      * Component maintains its configured {@link Ext.layout.container.Box#flex flex} value.
41858      */
41859
41860     hideMode: 'display',
41861     // Deprecate 5.0
41862     hideParent: false,
41863
41864     ariaRole: 'presentation',
41865
41866     bubbleEvents: [],
41867
41868     actionMode: 'el',
41869     monPropRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
41870
41871     //renderTpl: new Ext.XTemplate(
41872     //    '<div id="{id}" class="{baseCls} {cls} {cmpCls}<tpl if="typeof ui !== \'undefined\'"> {uiBase}-{ui}</tpl>"<tpl if="typeof style !== \'undefined\'"> style="{style}"</tpl>></div>', {
41873     //        compiled: true,
41874     //        disableFormats: true
41875     //    }
41876     //),
41877
41878     /**
41879      * Creates new Component.
41880      * @param {Ext.Element/String/Object} config The configuration options may be specified as either:
41881      *
41882      * - **an element** : it is set as the internal element and its id used as the component id
41883      * - **a string** : it is assumed to be the id of an existing element and is used as the component id
41884      * - **anything else** : it is assumed to be a standard config object and is applied to the component
41885      */
41886     constructor: function(config) {
41887         var me = this;
41888
41889         config = config || {};
41890         if (config.initialConfig) {
41891
41892             // Being initialized from an Ext.Action instance...
41893             if (config.isAction) {
41894                 me.baseAction = config;
41895             }
41896             config = config.initialConfig;
41897             // component cloning / action set up
41898         }
41899         else if (config.tagName || config.dom || Ext.isString(config)) {
41900             // element object
41901             config = {
41902                 applyTo: config,
41903                 id: config.id || config
41904             };
41905         }
41906
41907         me.callParent([config]);
41908
41909         // If we were configured from an instance of Ext.Action, (or configured with a baseAction option),
41910         // register this Component as one of its items
41911         if (me.baseAction){
41912             me.baseAction.addComponent(me);
41913         }
41914     },
41915
41916     /**
41917      * The initComponent template method is an important initialization step for a Component. It is intended to be
41918      * implemented by each subclass of Ext.Component to provide any needed constructor logic. The
41919      * initComponent method of the class being created is called first, with each initComponent method
41920      * up the hierarchy to Ext.Component being called thereafter. This makes it easy to implement and,
41921      * if needed, override the constructor logic of the Component at any step in the hierarchy.
41922      *
41923      * The initComponent method **must** contain a call to {@link Ext.Base#callParent callParent} in order
41924      * to ensure that the parent class' initComponent method is also called.
41925      *
41926      * The following example demonstrates using a dynamic string for the text of a button at the time of
41927      * instantiation of the class.
41928      *
41929      *     Ext.define('DynamicButtonText', {
41930      *         extend: 'Ext.button.Button',
41931      *
41932      *         initComponent: function() {
41933      *             this.text = new Date();
41934      *             this.renderTo = Ext.getBody();
41935      *             this.callParent();
41936      *         }
41937      *     });
41938      *
41939      *     Ext.onReady(function() {
41940      *         Ext.create('DynamicButtonText');
41941      *     });
41942      *
41943      * @template
41944      */
41945     initComponent: function() {
41946         var me = this;
41947
41948         me.callParent();
41949
41950         if (me.listeners) {
41951             me.on(me.listeners);
41952             delete me.listeners;
41953         }
41954         me.enableBubble(me.bubbleEvents);
41955         me.mons = [];
41956     },
41957
41958     // private
41959     afterRender: function() {
41960         var me = this,
41961             resizable = me.resizable;
41962
41963         if (me.floating) {
41964             me.makeFloating(me.floating);
41965         } else {
41966             me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]);
41967         }
41968
41969         if (Ext.isDefined(me.autoScroll)) {
41970             me.setAutoScroll(me.autoScroll);
41971         }
41972         me.callParent();
41973
41974         if (!(me.x && me.y) && (me.pageX || me.pageY)) {
41975             me.setPagePosition(me.pageX, me.pageY);
41976         }
41977
41978         if (resizable) {
41979             me.initResizable(resizable);
41980         }
41981
41982         if (me.draggable) {
41983             me.initDraggable();
41984         }
41985
41986         me.initAria();
41987     },
41988
41989     initAria: function() {
41990         var actionEl = this.getActionEl(),
41991             role = this.ariaRole;
41992         if (role) {
41993             actionEl.dom.setAttribute('role', role);
41994         }
41995     },
41996
41997     /**
41998      * Sets the overflow on the content element of the component.
41999      * @param {Boolean} scroll True to allow the Component to auto scroll.
42000      * @return {Ext.Component} this
42001      */
42002     setAutoScroll : function(scroll){
42003         var me = this,
42004             targetEl;
42005         scroll = !!scroll;
42006         if (me.rendered) {
42007             targetEl = me.getTargetEl();
42008             targetEl.setStyle('overflow', scroll ? 'auto' : '');
42009             if (scroll && (Ext.isIE6 || Ext.isIE7)) {
42010                 // The scrollable container element must be non-statically positioned or IE6/7 will make
42011                 // positioned children stay in place rather than scrolling with the rest of the content
42012                 targetEl.position();
42013             }
42014         }
42015         me.autoScroll = scroll;
42016         return me;
42017     },
42018
42019     // private
42020     makeFloating : function(cfg){
42021         this.mixins.floating.constructor.call(this, cfg);
42022     },
42023
42024     initResizable: function(resizable) {
42025         var me = this;
42026
42027         resizable = Ext.apply({
42028             target: me,
42029             dynamic: false,
42030             constrainTo: me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent()),
42031             handles: me.resizeHandles
42032         }, resizable);
42033         resizable.target = me;
42034         me.resizer = Ext.create('Ext.resizer.Resizer', resizable);
42035     },
42036
42037     getDragEl: function() {
42038         return this.el;
42039     },
42040
42041     initDraggable: function() {
42042         var me = this,
42043             ddConfig = Ext.applyIf({
42044                 el: me.getDragEl(),
42045                 constrainTo: me.constrain ? (me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.getScopeParent())) : undefined
42046             }, me.draggable);
42047
42048         // Add extra configs if Component is specified to be constrained
42049         if (me.constrain || me.constrainDelegate) {
42050             ddConfig.constrain = me.constrain;
42051             ddConfig.constrainDelegate = me.constrainDelegate;
42052         }
42053
42054         me.dd = Ext.create('Ext.util.ComponentDragger', me, ddConfig);
42055     },
42056
42057     /**
42058      * Sets the left and top of the component. To set the page XY position instead, use {@link #setPagePosition}. This
42059      * method fires the {@link #move} event.
42060      * @param {Number} left The new left
42061      * @param {Number} top The new top
42062      * @param {Boolean/Object} [animate] If true, the Component is _animated_ into its new position. You may also pass an
42063      * animation configuration.
42064      * @return {Ext.Component} this
42065      */
42066     setPosition: function(x, y, animate) {
42067         var me = this,
42068             el = me.el,
42069             to = {},
42070             adj, adjX, adjY, xIsNumber, yIsNumber;
42071
42072         if (Ext.isArray(x)) {
42073             animate = y;
42074             y = x[1];
42075             x = x[0];
42076         }
42077         me.x = x;
42078         me.y = y;
42079
42080         if (!me.rendered) {
42081             return me;
42082         }
42083
42084         adj = me.adjustPosition(x, y);
42085         adjX = adj.x;
42086         adjY = adj.y;
42087         xIsNumber = Ext.isNumber(adjX);
42088         yIsNumber = Ext.isNumber(adjY);
42089
42090         if (xIsNumber || yIsNumber) {
42091             if (animate) {
42092                 if (xIsNumber) {
42093                     to.left = adjX;
42094                 }
42095                 if (yIsNumber) {
42096                     to.top = adjY;
42097                 }
42098
42099                 me.stopAnimation();
42100                 me.animate(Ext.apply({
42101                     duration: 1000,
42102                     listeners: {
42103                         afteranimate: Ext.Function.bind(me.afterSetPosition, me, [adjX, adjY])
42104                     },
42105                     to: to
42106                 }, animate));
42107             }
42108             else {
42109                 if (!xIsNumber) {
42110                     el.setTop(adjY);
42111                 }
42112                 else if (!yIsNumber) {
42113                     el.setLeft(adjX);
42114                 }
42115                 else {
42116                     el.setLeftTop(adjX, adjY);
42117                 }
42118                 me.afterSetPosition(adjX, adjY);
42119             }
42120         }
42121         return me;
42122     },
42123
42124     /**
42125      * @private
42126      * @template
42127      * Template method called after a Component has been positioned.
42128      */
42129     afterSetPosition: function(ax, ay) {
42130         this.onPosition(ax, ay);
42131         this.fireEvent('move', this, ax, ay);
42132     },
42133
42134     /**
42135      * Displays component at specific xy position.
42136      * A floating component (like a menu) is positioned relative to its ownerCt if any.
42137      * Useful for popping up a context menu:
42138      *
42139      *     listeners: {
42140      *         itemcontextmenu: function(view, record, item, index, event, options) {
42141      *             Ext.create('Ext.menu.Menu', {
42142      *                 width: 100,
42143      *                 height: 100,
42144      *                 margin: '0 0 10 0',
42145      *                 items: [{
42146      *                     text: 'regular item 1'
42147      *                 },{
42148      *                     text: 'regular item 2'
42149      *                 },{
42150      *                     text: 'regular item 3'
42151      *                 }]
42152      *             }).showAt(event.getXY());
42153      *         }
42154      *     }
42155      *
42156      * @param {Number} x The new x position
42157      * @param {Number} y The new y position
42158      * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
42159      * animation configuration.
42160      */
42161     showAt: function(x, y, animate) {
42162         var me = this;
42163
42164         if (me.floating) {
42165             me.setPosition(x, y, animate);
42166         } else {
42167             me.setPagePosition(x, y, animate);
42168         }
42169         me.show();
42170     },
42171
42172     /**
42173      * Sets the page XY position of the component. To set the left and top instead, use {@link #setPosition}.
42174      * This method fires the {@link #move} event.
42175      * @param {Number} x The new x position
42176      * @param {Number} y The new y position
42177      * @param {Boolean/Object} [animate] True to animate the Component into its new position. You may also pass an
42178      * animation configuration.
42179      * @return {Ext.Component} this
42180      */
42181     setPagePosition: function(x, y, animate) {
42182         var me = this,
42183             p;
42184
42185         if (Ext.isArray(x)) {
42186             y = x[1];
42187             x = x[0];
42188         }
42189         me.pageX = x;
42190         me.pageY = y;
42191         if (me.floating && me.floatParent) {
42192             // Floating Components being positioned in their ownerCt have to be made absolute
42193             p = me.floatParent.getTargetEl().getViewRegion();
42194             if (Ext.isNumber(x) && Ext.isNumber(p.left)) {
42195                 x -= p.left;
42196             }
42197             if (Ext.isNumber(y) && Ext.isNumber(p.top)) {
42198                 y -= p.top;
42199             }
42200             me.setPosition(x, y, animate);
42201         }
42202         else {
42203             p = me.el.translatePoints(x, y);
42204             me.setPosition(p.left, p.top, animate);
42205         }
42206         return me;
42207     },
42208
42209     /**
42210      * Gets the current box measurements of the component's underlying element.
42211      * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
42212      * @return {Object} box An object in the format {x, y, width, height}
42213      */
42214     getBox : function(local){
42215         var pos = this.getPosition(local),
42216             size = this.getSize();
42217
42218         size.x = pos[0];
42219         size.y = pos[1];
42220         return size;
42221     },
42222
42223     /**
42224      * Sets the current box measurements of the component's underlying element.
42225      * @param {Object} box An object in the format {x, y, width, height}
42226      * @return {Ext.Component} this
42227      */
42228     updateBox : function(box){
42229         this.setSize(box.width, box.height);
42230         this.setPagePosition(box.x, box.y);
42231         return this;
42232     },
42233
42234     // Include margins
42235     getOuterSize: function() {
42236         var el = this.el;
42237         return {
42238             width: el.getWidth() + el.getMargin('lr'),
42239             height: el.getHeight() + el.getMargin('tb')
42240         };
42241     },
42242
42243     // private
42244     adjustPosition: function(x, y) {
42245
42246         // Floating Components being positioned in their ownerCt have to be made absolute
42247         if (this.floating && this.floatParent) {
42248             var o = this.floatParent.getTargetEl().getViewRegion();
42249             x += o.left;
42250             y += o.top;
42251         }
42252
42253         return {
42254             x: x,
42255             y: y
42256         };
42257     },
42258
42259     /**
42260      * Gets the current XY position of the component's underlying element.
42261      * @param {Boolean} [local=false] If true the element's left and top are returned instead of page XY.
42262      * @return {Number[]} The XY position of the element (e.g., [100, 200])
42263      */
42264     getPosition: function(local) {
42265         var me = this,
42266             el = me.el,
42267             xy,
42268             o;
42269
42270         // Floating Components which were just rendered with no ownerCt return local position.
42271         if ((local === true) || (me.floating && !me.floatParent)) {
42272             return [el.getLeft(true), el.getTop(true)];
42273         }
42274         xy = me.xy || el.getXY();
42275
42276         // Floating Components in an ownerCt have to have their positions made relative
42277         if (me.floating) {
42278             o = me.floatParent.getTargetEl().getViewRegion();
42279             xy[0] -= o.left;
42280             xy[1] -= o.top;
42281         }
42282         return xy;
42283     },
42284
42285     getId: function() {
42286         var me = this,
42287             xtype;
42288
42289         if (!me.id) {
42290             xtype = me.getXType();
42291             xtype = xtype ? xtype.replace(Ext.Component.INVALID_ID_CHARS_Re, '-') : 'ext-comp';
42292             me.id = xtype + '-' + me.getAutoId();
42293         }
42294         return me.id;
42295     },
42296
42297     onEnable: function() {
42298         var actionEl = this.getActionEl();
42299         actionEl.dom.removeAttribute('aria-disabled');
42300         actionEl.dom.disabled = false;
42301         this.callParent();
42302     },
42303
42304     onDisable: function() {
42305         var actionEl = this.getActionEl();
42306         actionEl.dom.setAttribute('aria-disabled', true);
42307         actionEl.dom.disabled = true;
42308         this.callParent();
42309     },
42310
42311     /**
42312      * Shows this Component, rendering it first if {@link #autoRender} or {@link #floating} are `true`.
42313      *
42314      * After being shown, a {@link #floating} Component (such as a {@link Ext.window.Window}), is activated it and
42315      * brought to the front of its {@link #zIndexManager z-index stack}.
42316      *
42317      * @param {String/Ext.Element} [animateTarget=null] **only valid for {@link #floating} Components such as {@link
42318      * Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
42319      * with `floating: true`.** The target from which the Component should animate from while opening.
42320      * @param {Function} [callback] A callback function to call after the Component is displayed.
42321      * Only necessary if animation was specified.
42322      * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
42323      * Defaults to this Component.
42324      * @return {Ext.Component} this
42325      */
42326     show: function(animateTarget, cb, scope) {
42327         var me = this;
42328
42329         if (me.rendered && me.isVisible()) {
42330             if (me.toFrontOnShow && me.floating) {
42331                 me.toFront();
42332             }
42333         } else if (me.fireEvent('beforeshow', me) !== false) {
42334             me.hidden = false;
42335
42336             // Render on first show if there is an autoRender config, or if this is a floater (Window, Menu, BoundList etc).
42337             if (!me.rendered && (me.autoRender || me.floating)) {
42338                 me.doAutoRender();
42339             }
42340             if (me.rendered) {
42341                 me.beforeShow();
42342                 me.onShow.apply(me, arguments);
42343
42344                 // Notify any owning Container unless it's suspended.
42345                 // Floating Components do not participate in layouts.
42346                 if (me.ownerCt && !me.floating && !(me.ownerCt.suspendLayout || me.ownerCt.layout.layoutBusy)) {
42347                     me.ownerCt.doLayout();
42348                 }
42349                 me.afterShow.apply(me, arguments);
42350             }
42351         }
42352         return me;
42353     },
42354
42355     beforeShow: Ext.emptyFn,
42356
42357     // Private. Override in subclasses where more complex behaviour is needed.
42358     onShow: function() {
42359         var me = this;
42360
42361         me.el.show();
42362         me.callParent(arguments);
42363         if (me.floating && me.constrain) {
42364             me.doConstrain();
42365         }
42366     },
42367
42368     afterShow: function(animateTarget, cb, scope) {
42369         var me = this,
42370             fromBox,
42371             toBox,
42372             ghostPanel;
42373
42374         // Default to configured animate target if none passed
42375         animateTarget = animateTarget || me.animateTarget;
42376
42377         // Need to be able to ghost the Component
42378         if (!me.ghost) {
42379             animateTarget = null;
42380         }
42381         // If we're animating, kick of an animation of the ghost from the target to the *Element* current box
42382         if (animateTarget) {
42383             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
42384             toBox = me.el.getBox();
42385             fromBox = animateTarget.getBox();
42386             me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets');
42387             ghostPanel = me.ghost();
42388             ghostPanel.el.stopAnimation();
42389
42390             // Shunting it offscreen immediately, *before* the Animation class grabs it ensure no flicker.
42391             ghostPanel.el.setX(-10000);
42392
42393             ghostPanel.el.animate({
42394                 from: fromBox,
42395                 to: toBox,
42396                 listeners: {
42397                     afteranimate: function() {
42398                         delete ghostPanel.componentLayout.lastComponentSize;
42399                         me.unghost();
42400                         me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets');
42401                         me.onShowComplete(cb, scope);
42402                     }
42403                 }
42404             });
42405         }
42406         else {
42407             me.onShowComplete(cb, scope);
42408         }
42409     },
42410
42411     onShowComplete: function(cb, scope) {
42412         var me = this;
42413         if (me.floating) {
42414             me.toFront();
42415         }
42416         Ext.callback(cb, scope || me);
42417         me.fireEvent('show', me);
42418     },
42419
42420     /**
42421      * Hides this Component, setting it to invisible using the configured {@link #hideMode}.
42422      * @param {String/Ext.Element/Ext.Component} [animateTarget=null] **only valid for {@link #floating} Components
42423      * such as {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have
42424      * been configured with `floating: true`.**. The target to which the Component should animate while hiding.
42425      * @param {Function} [callback] A callback function to call after the Component is hidden.
42426      * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
42427      * Defaults to this Component.
42428      * @return {Ext.Component} this
42429      */
42430     hide: function() {
42431         var me = this;
42432
42433         // Clear the flag which is set if a floatParent was hidden while this is visible.
42434         // If a hide operation was subsequently called, that pending show must be hidden.
42435         me.showOnParentShow = false;
42436
42437         if (!(me.rendered && !me.isVisible()) && me.fireEvent('beforehide', me) !== false) {
42438             me.hidden = true;
42439             if (me.rendered) {
42440                 me.onHide.apply(me, arguments);
42441
42442                 // Notify any owning Container unless it's suspended.
42443                 // Floating Components do not participate in layouts.
42444                 if (me.ownerCt && !me.floating && !(me.ownerCt.suspendLayout || me.ownerCt.layout.layoutBusy)) {
42445                     me.ownerCt.doLayout();
42446                 }
42447             }
42448         }
42449         return me;
42450     },
42451
42452     // Possibly animate down to a target element.
42453     onHide: function(animateTarget, cb, scope) {
42454         var me = this,
42455             ghostPanel,
42456             toBox;
42457
42458         // Default to configured animate target if none passed
42459         animateTarget = animateTarget || me.animateTarget;
42460
42461         // Need to be able to ghost the Component
42462         if (!me.ghost) {
42463             animateTarget = null;
42464         }
42465         // If we're animating, kick off an animation of the ghost down to the target
42466         if (animateTarget) {
42467             animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
42468             ghostPanel = me.ghost();
42469             ghostPanel.el.stopAnimation();
42470             toBox = animateTarget.getBox();
42471             toBox.width += 'px';
42472             toBox.height += 'px';
42473             ghostPanel.el.animate({
42474                 to: toBox,
42475                 listeners: {
42476                     afteranimate: function() {
42477                         delete ghostPanel.componentLayout.lastComponentSize;
42478                         ghostPanel.el.hide();
42479                         me.afterHide(cb, scope);
42480                     }
42481                 }
42482             });
42483         }
42484         me.el.hide();
42485         if (!animateTarget) {
42486             me.afterHide(cb, scope);
42487         }
42488     },
42489
42490     afterHide: function(cb, scope) {
42491         Ext.callback(cb, scope || this);
42492         this.fireEvent('hide', this);
42493     },
42494
42495     /**
42496      * @private
42497      * @template
42498      * Template method to contribute functionality at destroy time.
42499      */
42500     onDestroy: function() {
42501         var me = this;
42502
42503         // Ensure that any ancillary components are destroyed.
42504         if (me.rendered) {
42505             Ext.destroy(
42506                 me.proxy,
42507                 me.proxyWrap,
42508                 me.resizer
42509             );
42510             // Different from AbstractComponent
42511             if (me.actionMode == 'container' || me.removeMode == 'container') {
42512                 me.container.remove();
42513             }
42514         }
42515         delete me.focusTask;
42516         me.callParent();
42517     },
42518
42519     deleteMembers: function() {
42520         var args = arguments,
42521             len = args.length,
42522             i = 0;
42523         for (; i < len; ++i) {
42524             delete this[args[i]];
42525         }
42526     },
42527
42528     /**
42529      * Try to focus this component.
42530      * @param {Boolean} [selectText] If applicable, true to also select the text in this component
42531      * @param {Boolean/Number} [delay] Delay the focus this number of milliseconds (true for 10 milliseconds).
42532      * @return {Ext.Component} this
42533      */
42534     focus: function(selectText, delay) {
42535         var me = this,
42536                 focusEl;
42537
42538         if (delay) {
42539             if (!me.focusTask) {
42540                 me.focusTask = Ext.create('Ext.util.DelayedTask', me.focus);
42541             }
42542             me.focusTask.delay(Ext.isNumber(delay) ? delay : 10, null, me, [selectText, false]);
42543             return me;
42544         }
42545
42546         if (me.rendered && !me.isDestroyed) {
42547             // getFocusEl could return a Component.
42548             focusEl = me.getFocusEl();
42549             focusEl.focus();
42550             if (focusEl.dom && selectText === true) {
42551                 focusEl.dom.select();
42552             }
42553
42554             // Focusing a floating Component brings it to the front of its stack.
42555             // this is performed by its zIndexManager. Pass preventFocus true to avoid recursion.
42556             if (me.floating) {
42557                 me.toFront(true);
42558             }
42559         }
42560         return me;
42561     },
42562
42563     /**
42564      * @private
42565      * Returns the focus holder element associated with this Component. By default, this is the Component's encapsulating
42566      * element. Subclasses which use embedded focusable elements (such as Window and Button) should override this for use
42567      * by the {@link #focus} method.
42568      * @returns {Ext.Element} the focus holing element.
42569      */
42570     getFocusEl: function() {
42571         return this.el;
42572     },
42573
42574     // private
42575     blur: function() {
42576         if (this.rendered) {
42577             this.getFocusEl().blur();
42578         }
42579         return this;
42580     },
42581
42582     getEl: function() {
42583         return this.el;
42584     },
42585
42586     // Deprecate 5.0
42587     getResizeEl: function() {
42588         return this.el;
42589     },
42590
42591     // Deprecate 5.0
42592     getPositionEl: function() {
42593         return this.el;
42594     },
42595
42596     // Deprecate 5.0
42597     getActionEl: function() {
42598         return this.el;
42599     },
42600
42601     // Deprecate 5.0
42602     getVisibilityEl: function() {
42603         return this.el;
42604     },
42605
42606     // Deprecate 5.0
42607     onResize: Ext.emptyFn,
42608
42609     // private
42610     getBubbleTarget: function() {
42611         return this.ownerCt;
42612     },
42613
42614     // private
42615     getContentTarget: function() {
42616         return this.el;
42617     },
42618
42619     /**
42620      * Clone the current component using the original config values passed into this instance by default.
42621      * @param {Object} overrides A new config containing any properties to override in the cloned version.
42622      * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
42623      * @return {Ext.Component} clone The cloned copy of this component
42624      */
42625     cloneConfig: function(overrides) {
42626         overrides = overrides || {};
42627         var id = overrides.id || Ext.id(),
42628             cfg = Ext.applyIf(overrides, this.initialConfig),
42629             self;
42630
42631         cfg.id = id;
42632
42633         self = Ext.getClass(this);
42634
42635         // prevent dup id
42636         return new self(cfg);
42637     },
42638
42639     /**
42640      * Gets the xtype for this component as registered with {@link Ext.ComponentManager}. For a list of all available
42641      * xtypes, see the {@link Ext.Component} header. Example usage:
42642      *
42643      *     var t = new Ext.form.field.Text();
42644      *     alert(t.getXType());  // alerts 'textfield'
42645      *
42646      * @return {String} The xtype
42647      */
42648     getXType: function() {
42649         return this.self.xtype;
42650     },
42651
42652     /**
42653      * Find a container above this component at any level by a custom function. If the passed function returns true, the
42654      * container will be returned.
42655      * @param {Function} fn The custom function to call with the arguments (container, this component).
42656      * @return {Ext.container.Container} The first Container for which the custom function returns true
42657      */
42658     findParentBy: function(fn) {
42659         var p;
42660
42661         // Iterate up the ownerCt chain until there's no ownerCt, or we find an ancestor which matches using the selector function.
42662         for (p = this.ownerCt; p && !fn(p, this); p = p.ownerCt);
42663         return p || null;
42664     },
42665
42666     /**
42667      * Find a container above this component at any level by xtype or class
42668      *
42669      * See also the {@link Ext.Component#up up} method.
42670      *
42671      * @param {String/Ext.Class} xtype The xtype string for a component, or the class of the component directly
42672      * @return {Ext.container.Container} The first Container which matches the given xtype or class
42673      */
42674     findParentByType: function(xtype) {
42675         return Ext.isFunction(xtype) ?
42676             this.findParentBy(function(p) {
42677                 return p.constructor === xtype;
42678             })
42679         :
42680             this.up(xtype);
42681     },
42682
42683     /**
42684      * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope
42685      * (*this*) of function call will be the scope provided or the current component. The arguments to the function will
42686      * be the args provided or the current component. If the function returns false at any point, the bubble is stopped.
42687      *
42688      * @param {Function} fn The function to call
42689      * @param {Object} [scope] The scope of the function. Defaults to current node.
42690      * @param {Array} [args] The args to call the function with. Defaults to passing the current component.
42691      * @return {Ext.Component} this
42692      */
42693     bubble: function(fn, scope, args) {
42694         var p = this;
42695         while (p) {
42696             if (fn.apply(scope || p, args || [p]) === false) {
42697                 break;
42698             }
42699             p = p.ownerCt;
42700         }
42701         return this;
42702     },
42703
42704     getProxy: function() {
42705         var me = this,
42706             target;
42707
42708         if (!me.proxy) {
42709             target = Ext.getBody();
42710             if (Ext.scopeResetCSS) {
42711                 me.proxyWrap = target = Ext.getBody().createChild({
42712                     cls: Ext.baseCSSPrefix + 'reset'
42713                 });
42714             }
42715             me.proxy = me.el.createProxy(Ext.baseCSSPrefix + 'proxy-el', target, true);
42716         }
42717         return me.proxy;
42718     }
42719
42720 });
42721
42722 /**
42723  * @class Ext.layout.container.AbstractContainer
42724  * @extends Ext.layout.Layout
42725  * Please refer to sub classes documentation
42726  * @private
42727  */
42728 Ext.define('Ext.layout.container.AbstractContainer', {
42729
42730     /* Begin Definitions */
42731
42732     extend: 'Ext.layout.Layout',
42733
42734     /* End Definitions */
42735
42736     type: 'container',
42737
42738     /**
42739      * @cfg {Boolean} bindToOwnerCtComponent
42740      * Flag to notify the ownerCt Component on afterLayout of a change
42741      */
42742     bindToOwnerCtComponent: false,
42743
42744     /**
42745      * @cfg {Boolean} bindToOwnerCtContainer
42746      * Flag to notify the ownerCt Container on afterLayout of a change
42747      */
42748     bindToOwnerCtContainer: false,
42749
42750     /**
42751      * @cfg {String} itemCls
42752      * <p>An optional extra CSS class that will be added to the container. This can be useful for adding
42753      * customized styles to the container or any of its children using standard CSS rules. See
42754      * {@link Ext.Component}.{@link Ext.Component#componentCls componentCls} also.</p>
42755      * </p>
42756      */
42757
42758     /**
42759     * Set the size of an item within the Container.  We should always use setCalculatedSize.
42760     * @private
42761     */
42762     setItemSize: function(item, width, height) {
42763         if (Ext.isObject(width)) {
42764             height = width.height;
42765             width = width.width;
42766         }
42767         item.setCalculatedSize(width, height, this.owner);
42768     },
42769
42770     /**
42771      * <p>Returns an array of child components either for a render phase (Performed in the beforeLayout method of the layout's
42772      * base class), or the layout phase (onLayout).</p>
42773      * @return {Ext.Component[]} of child components
42774      */
42775     getLayoutItems: function() {
42776         return this.owner && this.owner.items && this.owner.items.items || [];
42777     },
42778
42779     /**
42780      * Containers should not lay out child components when collapsed.
42781      */
42782     beforeLayout: function() {
42783         return !this.owner.collapsed && this.callParent(arguments);
42784     },
42785
42786     afterLayout: function() {
42787         this.owner.afterLayout(this);
42788     },
42789     /**
42790      * Returns the owner component's resize element.
42791      * @return {Ext.Element}
42792      */
42793      getTarget: function() {
42794          return this.owner.getTargetEl();
42795      },
42796     /**
42797      * <p>Returns the element into which rendering must take place. Defaults to the owner Container's target element.</p>
42798      * May be overridden in layout managers which implement an inner element.
42799      * @return {Ext.Element}
42800      */
42801      getRenderTarget: function() {
42802          return this.owner.getTargetEl();
42803      }
42804 });
42805
42806 /**
42807 * @class Ext.layout.container.Container
42808 * @extends Ext.layout.container.AbstractContainer
42809 * <p>This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
42810 * configuration property.  See {@link Ext.container.Container#layout} for additional details.</p>
42811 */
42812 Ext.define('Ext.layout.container.Container', {
42813
42814     /* Begin Definitions */
42815
42816     extend: 'Ext.layout.container.AbstractContainer',
42817     alternateClassName: 'Ext.layout.ContainerLayout',
42818
42819     /* End Definitions */
42820
42821     layoutItem: function(item, box) {
42822         if (box) {
42823             item.doComponentLayout(box.width, box.height);
42824         } else {
42825             item.doComponentLayout();
42826         }
42827     },
42828
42829     getLayoutTargetSize : function() {
42830         var target = this.getTarget(),
42831             ret;
42832
42833         if (target) {
42834             ret = target.getViewSize();
42835
42836             // IE in will sometimes return a width of 0 on the 1st pass of getViewSize.
42837             // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
42838             // with getViewSize
42839             if (Ext.isIE && ret.width == 0){
42840                 ret = target.getStyleSize();
42841             }
42842
42843             ret.width -= target.getPadding('lr');
42844             ret.height -= target.getPadding('tb');
42845         }
42846         return ret;
42847     },
42848
42849     beforeLayout: function() {
42850         if (this.owner.beforeLayout(arguments) !== false) {
42851             return this.callParent(arguments);
42852         }
42853         else {
42854             return false;
42855         }
42856     },
42857
42858     /**
42859      * @protected
42860      * Returns all items that are rendered
42861      * @return {Array} All matching items
42862      */
42863     getRenderedItems: function() {
42864         var me = this,
42865             target = me.getTarget(),
42866             items = me.getLayoutItems(),
42867             ln = items.length,
42868             renderedItems = [],
42869             i, item;
42870
42871         for (i = 0; i < ln; i++) {
42872             item = items[i];
42873             if (item.rendered && me.isValidParent(item, target, i)) {
42874                 renderedItems.push(item);
42875             }
42876         }
42877
42878         return renderedItems;
42879     },
42880
42881     /**
42882      * @protected
42883      * Returns all items that are both rendered and visible
42884      * @return {Array} All matching items
42885      */
42886     getVisibleItems: function() {
42887         var target   = this.getTarget(),
42888             items = this.getLayoutItems(),
42889             ln = items.length,
42890             visibleItems = [],
42891             i, item;
42892
42893         for (i = 0; i < ln; i++) {
42894             item = items[i];
42895             if (item.rendered && this.isValidParent(item, target, i) && item.hidden !== true) {
42896                 visibleItems.push(item);
42897             }
42898         }
42899
42900         return visibleItems;
42901     }
42902 });
42903 /**
42904  * @class Ext.layout.container.Auto
42905  * @extends Ext.layout.container.Container
42906  *
42907  * The AutoLayout is the default layout manager delegated by {@link Ext.container.Container} to
42908  * render any child Components when no `{@link Ext.container.Container#layout layout}` is configured into
42909  * a `{@link Ext.container.Container Container}.` AutoLayout provides only a passthrough of any layout calls
42910  * to any child containers.
42911  *
42912  *     @example
42913  *     Ext.create('Ext.Panel', {
42914  *         width: 500,
42915  *         height: 280,
42916  *         title: "AutoLayout Panel",
42917  *         layout: 'auto',
42918  *         renderTo: document.body,
42919  *         items: [{
42920  *             xtype: 'panel',
42921  *             title: 'Top Inner Panel',
42922  *             width: '75%',
42923  *             height: 90
42924  *         },
42925  *         {
42926  *             xtype: 'panel',
42927  *             title: 'Bottom Inner Panel',
42928  *             width: '75%',
42929  *             height: 90
42930  *         }]
42931  *     });
42932  */
42933 Ext.define('Ext.layout.container.Auto', {
42934
42935     /* Begin Definitions */
42936
42937     alias: ['layout.auto', 'layout.autocontainer'],
42938
42939     extend: 'Ext.layout.container.Container',
42940
42941     /* End Definitions */
42942
42943     type: 'autocontainer',
42944
42945     bindToOwnerCtComponent: true,
42946
42947     // @private
42948     onLayout : function(owner, target) {
42949         var me = this,
42950             items = me.getLayoutItems(),
42951             ln = items.length,
42952             i;
42953
42954         // Ensure the Container is only primed with the clear element if there are child items.
42955         if (ln) {
42956             // Auto layout uses natural HTML flow to arrange the child items.
42957             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
42958             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
42959             if (!me.clearEl) {
42960                 me.clearEl = me.getRenderTarget().createChild({
42961                     cls: Ext.baseCSSPrefix + 'clear',
42962                     role: 'presentation'
42963                 });
42964             }
42965
42966             // Auto layout allows CSS to size its child items.
42967             for (i = 0; i < ln; i++) {
42968                 me.setItemSize(items[i]);
42969             }
42970         }
42971     },
42972
42973     configureItem: function(item) {
42974         this.callParent(arguments);
42975
42976         // Auto layout does not manage any dimensions.
42977         item.layoutManagedHeight = 2;
42978         item.layoutManagedWidth = 2;
42979     }
42980 });
42981 /**
42982  * @class Ext.container.AbstractContainer
42983  * @extends Ext.Component
42984  * An abstract base class which provides shared methods for Containers across the Sencha product line.
42985  * @private
42986  */
42987 Ext.define('Ext.container.AbstractContainer', {
42988
42989     /* Begin Definitions */
42990
42991     extend: 'Ext.Component',
42992
42993     requires: [
42994         'Ext.util.MixedCollection',
42995         'Ext.layout.container.Auto',
42996         'Ext.ZIndexManager'
42997     ],
42998
42999     /* End Definitions */
43000     /**
43001      * @cfg {String/Object} layout
43002      * <p><b>Important</b>: In order for child items to be correctly sized and
43003      * positioned, typically a layout manager <b>must</b> be specified through
43004      * the <code>layout</code> configuration option.</p>
43005      * <p>The sizing and positioning of child {@link #items} is the responsibility of
43006      * the Container's layout manager which creates and manages the type of layout
43007      * you have in mind.  For example:</p>
43008      * <p>If the {@link #layout} configuration is not explicitly specified for
43009      * a general purpose container (e.g. Container or Panel) the
43010      * {@link Ext.layout.container.Auto default layout manager} will be used
43011      * which does nothing but render child components sequentially into the
43012      * Container (no sizing or positioning will be performed in this situation).</p>
43013      * <p><b><code>layout</code></b> may be specified as either as an Object or as a String:</p>
43014      * <div><ul class="mdetail-params">
43015      * <li><u>Specify as an Object</u></li>
43016      * <div><ul class="mdetail-params">
43017      * <li>Example usage:</li>
43018      * <pre><code>
43019 layout: {
43020     type: 'vbox',
43021     align: 'left'
43022 }
43023        </code></pre>
43024      *
43025      * <li><code><b>type</b></code></li>
43026      * <br/><p>The layout type to be used for this container.  If not specified,
43027      * a default {@link Ext.layout.container.Auto} will be created and used.</p>
43028      * <p>Valid layout <code>type</code> values are:</p>
43029      * <div class="sub-desc"><ul class="mdetail-params">
43030      * <li><code><b>{@link Ext.layout.container.Auto Auto}</b></code> &nbsp;&nbsp;&nbsp; <b>Default</b></li>
43031      * <li><code><b>{@link Ext.layout.container.Card card}</b></code></li>
43032      * <li><code><b>{@link Ext.layout.container.Fit fit}</b></code></li>
43033      * <li><code><b>{@link Ext.layout.container.HBox hbox}</b></code></li>
43034      * <li><code><b>{@link Ext.layout.container.VBox vbox}</b></code></li>
43035      * <li><code><b>{@link Ext.layout.container.Anchor anchor}</b></code></li>
43036      * <li><code><b>{@link Ext.layout.container.Table table}</b></code></li>
43037      * </ul></div>
43038      *
43039      * <li>Layout specific configuration properties</li>
43040      * <p>Additional layout specific configuration properties may also be
43041      * specified. For complete details regarding the valid config options for
43042      * each layout type, see the layout class corresponding to the <code>type</code>
43043      * specified.</p>
43044      *
43045      * </ul></div>
43046      *
43047      * <li><u>Specify as a String</u></li>
43048      * <div><ul class="mdetail-params">
43049      * <li>Example usage:</li>
43050      * <pre><code>
43051 layout: 'vbox'
43052        </code></pre>
43053      * <li><code><b>layout</b></code></li>
43054      * <p>The layout <code>type</code> to be used for this container (see list
43055      * of valid layout type values above).</p>
43056      * <p>Additional layout specific configuration properties. For complete
43057      * details regarding the valid config options for each layout type, see the
43058      * layout class corresponding to the <code>layout</code> specified.</p>
43059      * </ul></div></ul></div>
43060      */
43061
43062     /**
43063      * @cfg {String/Number} activeItem
43064      * A string component id or the numeric index of the component that should be initially activated within the
43065      * container's layout on render.  For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
43066      * item in the container's collection).  activeItem only applies to layout styles that can display
43067      * items one at a time (like {@link Ext.layout.container.Card} and {@link Ext.layout.container.Fit}).
43068      */
43069     /**
43070      * @cfg {Object/Object[]} items
43071      * <p>A single item, or an array of child Components to be added to this container</p>
43072      * <p><b>Unless configured with a {@link #layout}, a Container simply renders child Components serially into
43073      * its encapsulating element and performs no sizing or positioning upon them.</b><p>
43074      * <p>Example:</p>
43075      * <pre><code>
43076 // specifying a single item
43077 items: {...},
43078 layout: 'fit',    // The single items is sized to fit
43079
43080 // specifying multiple items
43081 items: [{...}, {...}],
43082 layout: 'hbox', // The items are arranged horizontally
43083        </code></pre>
43084      * <p>Each item may be:</p>
43085      * <ul>
43086      * <li>A {@link Ext.Component Component}</li>
43087      * <li>A Component configuration object</li>
43088      * </ul>
43089      * <p>If a configuration object is specified, the actual type of Component to be
43090      * instantiated my be indicated by using the {@link Ext.Component#xtype xtype} option.</p>
43091      * <p>Every Component class has its own {@link Ext.Component#xtype xtype}.</p>
43092      * <p>If an {@link Ext.Component#xtype xtype} is not explicitly
43093      * specified, the {@link #defaultType} for the Container is used, which by default is usually <code>panel</code>.</p>
43094      * <p><b>Notes</b>:</p>
43095      * <p>Ext uses lazy rendering. Child Components will only be rendered
43096      * should it become necessary. Items are automatically laid out when they are first
43097      * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</p>
43098      * <p>Do not specify <code>{@link Ext.panel.Panel#contentEl contentEl}</code> or
43099      * <code>{@link Ext.panel.Panel#html html}</code> with <code>items</code>.</p>
43100      */
43101     /**
43102      * @cfg {Object/Function} defaults
43103      * This option is a means of applying default settings to all added items whether added through the {@link #items}
43104      * config or via the {@link #add} or {@link #insert} methods.
43105      *
43106      * Defaults are applied to both config objects and instantiated components conditionally so as not to override
43107      * existing properties in the item (see {@link Ext#applyIf}).
43108      *
43109      * If the defaults option is specified as a function, then the function will be called using this Container as the
43110      * scope (`this` reference) and passing the added item as the first parameter. Any resulting object
43111      * from that call is then applied to the item as default properties.
43112      *
43113      * For example, to automatically apply padding to the body of each of a set of
43114      * contained {@link Ext.panel.Panel} items, you could pass: `defaults: {bodyStyle:'padding:15px'}`.
43115      *
43116      * Usage:
43117      *
43118      *     defaults: { // defaults are applied to items, not the container
43119      *         autoScroll: true
43120      *     },
43121      *     items: [
43122      *         // default will not be applied here, panel1 will be autoScroll: false
43123      *         {
43124      *             xtype: 'panel',
43125      *             id: 'panel1',
43126      *             autoScroll: false
43127      *         },
43128      *         // this component will have autoScroll: true
43129      *         new Ext.panel.Panel({
43130      *             id: 'panel2'
43131      *         })
43132      *     ]
43133      */
43134
43135     /** @cfg {Boolean} suspendLayout
43136      * If true, suspend calls to doLayout.  Useful when batching multiple adds to a container and not passing them
43137      * as multiple arguments or an array.
43138      */
43139     suspendLayout : false,
43140
43141     /** @cfg {Boolean} autoDestroy
43142      * If true the container will automatically destroy any contained component that is removed from it, else
43143      * destruction must be handled manually.
43144      * Defaults to true.
43145      */
43146     autoDestroy : true,
43147
43148      /** @cfg {String} defaultType
43149       * <p>The default {@link Ext.Component xtype} of child Components to create in this Container when
43150       * a child item is specified as a raw configuration object, rather than as an instantiated Component.</p>
43151       * <p>Defaults to <code>'panel'</code>.</p>
43152       */
43153     defaultType: 'panel',
43154
43155     isContainer : true,
43156
43157     /**
43158      * The number of container layout calls made on this object.
43159      * @property layoutCounter
43160      * @type {Number}
43161      * @private
43162      */
43163     layoutCounter : 0,
43164
43165     baseCls: Ext.baseCSSPrefix + 'container',
43166
43167     /**
43168      * @cfg {String[]} bubbleEvents
43169      * <p>An array of events that, when fired, should be bubbled to any parent container.
43170      * See {@link Ext.util.Observable#enableBubble}.
43171      * Defaults to <code>['add', 'remove']</code>.
43172      */
43173     bubbleEvents: ['add', 'remove'],
43174
43175     // @private
43176     initComponent : function(){
43177         var me = this;
43178         me.addEvents(
43179             /**
43180              * @event afterlayout
43181              * Fires when the components in this container are arranged by the associated layout manager.
43182              * @param {Ext.container.Container} this
43183              * @param {Ext.layout.container.Container} layout The ContainerLayout implementation for this container
43184              */
43185             'afterlayout',
43186             /**
43187              * @event beforeadd
43188              * Fires before any {@link Ext.Component} is added or inserted into the container.
43189              * A handler can return false to cancel the add.
43190              * @param {Ext.container.Container} this
43191              * @param {Ext.Component} component The component being added
43192              * @param {Number} index The index at which the component will be added to the container's items collection
43193              */
43194             'beforeadd',
43195             /**
43196              * @event beforeremove
43197              * Fires before any {@link Ext.Component} is removed from the container.  A handler can return
43198              * false to cancel the remove.
43199              * @param {Ext.container.Container} this
43200              * @param {Ext.Component} component The component being removed
43201              */
43202             'beforeremove',
43203             /**
43204              * @event add
43205              * @bubbles
43206              * Fires after any {@link Ext.Component} is added or inserted into the container.
43207              * @param {Ext.container.Container} this
43208              * @param {Ext.Component} component The component that was added
43209              * @param {Number} index The index at which the component was added to the container's items collection
43210              */
43211             'add',
43212             /**
43213              * @event remove
43214              * @bubbles
43215              * Fires after any {@link Ext.Component} is removed from the container.
43216              * @param {Ext.container.Container} this
43217              * @param {Ext.Component} component The component that was removed
43218              */
43219             'remove'
43220         );
43221
43222         // layoutOnShow stack
43223         me.layoutOnShow = Ext.create('Ext.util.MixedCollection');
43224         me.callParent();
43225         me.initItems();
43226     },
43227
43228     // @private
43229     initItems : function() {
43230         var me = this,
43231             items = me.items;
43232
43233         /**
43234          * The MixedCollection containing all the child items of this container.
43235          * @property items
43236          * @type Ext.util.MixedCollection
43237          */
43238         me.items = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
43239
43240         if (items) {
43241             if (!Ext.isArray(items)) {
43242                 items = [items];
43243             }
43244
43245             me.add(items);
43246         }
43247     },
43248
43249     // @private
43250     afterRender : function() {
43251         this.getLayout();
43252         this.callParent();
43253     },
43254
43255     renderChildren: function () {
43256         var me = this,
43257             layout = me.getLayout();
43258
43259         me.callParent();
43260         // this component's elements exist, so now create the child components' elements
43261
43262         if (layout) {
43263             me.suspendLayout = true;
43264             layout.renderChildren();
43265             delete me.suspendLayout;
43266         }
43267     },
43268
43269     // @private
43270     setLayout : function(layout) {
43271         var currentLayout = this.layout;
43272
43273         if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
43274             currentLayout.setOwner(null);
43275         }
43276
43277         this.layout = layout;
43278         layout.setOwner(this);
43279     },
43280
43281     /**
43282      * Returns the {@link Ext.layout.container.AbstractContainer layout} instance currently associated with this Container.
43283      * If a layout has not been instantiated yet, that is done first
43284      * @return {Ext.layout.container.AbstractContainer} The layout
43285      */
43286     getLayout : function() {
43287         var me = this;
43288         if (!me.layout || !me.layout.isLayout) {
43289             me.setLayout(Ext.layout.Layout.create(me.layout, 'autocontainer'));
43290         }
43291
43292         return me.layout;
43293     },
43294
43295     /**
43296      * Manually force this container's layout to be recalculated. The framework uses this internally to refresh layouts
43297      * form most cases.
43298      * @return {Ext.container.Container} this
43299      */
43300     doLayout : function() {
43301         var me = this,
43302             layout = me.getLayout();
43303
43304         if (me.rendered && layout && !me.suspendLayout) {
43305             // If either dimension is being auto-set, then it requires a ComponentLayout to be run.
43306             if (!me.isFixedWidth() || !me.isFixedHeight()) {
43307                 // Only run the ComponentLayout if it is not already in progress
43308                 if (me.componentLayout.layoutBusy !== true) {
43309                     me.doComponentLayout();
43310                     if (me.componentLayout.layoutCancelled === true) {
43311                         layout.layout();
43312                     }
43313                 }
43314             }
43315             // Both dimensions set, either by configuration, or by an owning layout, run a ContainerLayout
43316             else {
43317                 // Only run the ContainerLayout if it is not already in progress
43318                 if (layout.layoutBusy !== true) {
43319                     layout.layout();
43320                 }
43321             }
43322         }
43323
43324         return me;
43325     },
43326
43327     // @private
43328     afterLayout : function(layout) {
43329         ++this.layoutCounter;
43330         this.fireEvent('afterlayout', this, layout);
43331     },
43332
43333     // @private
43334     prepareItems : function(items, applyDefaults) {
43335         if (!Ext.isArray(items)) {
43336             items = [items];
43337         }
43338
43339         // Make sure defaults are applied and item is initialized
43340         var i = 0,
43341             len = items.length,
43342             item;
43343
43344         for (; i < len; i++) {
43345             item = items[i];
43346             if (applyDefaults) {
43347                 item = this.applyDefaults(item);
43348             }
43349             items[i] = this.lookupComponent(item);
43350         }
43351         return items;
43352     },
43353
43354     // @private
43355     applyDefaults : function(config) {
43356         var defaults = this.defaults;
43357
43358         if (defaults) {
43359             if (Ext.isFunction(defaults)) {
43360                 defaults = defaults.call(this, config);
43361             }
43362
43363             if (Ext.isString(config)) {
43364                 config = Ext.ComponentManager.get(config);
43365             }
43366             Ext.applyIf(config, defaults);
43367         }
43368
43369         return config;
43370     },
43371
43372     // @private
43373     lookupComponent : function(comp) {
43374         return Ext.isString(comp) ? Ext.ComponentManager.get(comp) : this.createComponent(comp);
43375     },
43376
43377     // @private
43378     createComponent : function(config, defaultType) {
43379         // // add in ownerCt at creation time but then immediately
43380         // // remove so that onBeforeAdd can handle it
43381         // var component = Ext.create(Ext.apply({ownerCt: this}, config), defaultType || this.defaultType);
43382         //
43383         // delete component.initialConfig.ownerCt;
43384         // delete component.ownerCt;
43385
43386         return Ext.ComponentManager.create(config, defaultType || this.defaultType);
43387     },
43388
43389     // @private - used as the key lookup function for the items collection
43390     getComponentId : function(comp) {
43391         return comp.getItemId();
43392     },
43393
43394     /**
43395
43396 Adds {@link Ext.Component Component}(s) to this Container.
43397
43398 ##Description:##
43399
43400 - Fires the {@link #beforeadd} event before adding.
43401 - The Container's {@link #defaults default config values} will be applied
43402   accordingly (see `{@link #defaults}` for details).
43403 - Fires the `{@link #add}` event after the component has been added.
43404
43405 ##Notes:##
43406
43407 If the Container is __already rendered__ when `add`
43408 is called, it will render the newly added Component into its content area.
43409
43410 __**If**__ the Container was configured with a size-managing {@link #layout} manager, the Container
43411 will recalculate its internal layout at this time too.
43412
43413 Note that the default layout manager simply renders child Components sequentially into the content area and thereafter performs no sizing.
43414
43415 If adding multiple new child Components, pass them as an array to the `add` method, so that only one layout recalculation is performed.
43416
43417     tb = new {@link Ext.toolbar.Toolbar}({
43418         renderTo: document.body
43419     });  // toolbar is rendered
43420     tb.add([{text:'Button 1'}, {text:'Button 2'}]); // add multiple items. ({@link #defaultType} for {@link Ext.toolbar.Toolbar Toolbar} is 'button')
43421
43422 ##Warning:##
43423
43424 Components directly managed by the BorderLayout layout manager
43425 may not be removed or added.  See the Notes for {@link Ext.layout.container.Border BorderLayout}
43426 for more details.
43427
43428      * @param {Ext.Component[]/Ext.Component...} component
43429      * Either one or more Components to add or an Array of Components to add.
43430      * See `{@link #items}` for additional information.
43431      *
43432      * @return {Ext.Component[]/Ext.Component} The Components that were added.
43433      * @markdown
43434      */
43435     add : function() {
43436         var me = this,
43437             args = Array.prototype.slice.call(arguments),
43438             hasMultipleArgs,
43439             items,
43440             results = [],
43441             i,
43442             ln,
43443             item,
43444             index = -1,
43445             cmp;
43446
43447         if (typeof args[0] == 'number') {
43448             index = args.shift();
43449         }
43450
43451         hasMultipleArgs = args.length > 1;
43452         if (hasMultipleArgs || Ext.isArray(args[0])) {
43453
43454             items = hasMultipleArgs ? args : args[0];
43455             // Suspend Layouts while we add multiple items to the container
43456             me.suspendLayout = true;
43457             for (i = 0, ln = items.length; i < ln; i++) {
43458                 item = items[i];
43459
43460
43461                 if (index != -1) {
43462                     item = me.add(index + i, item);
43463                 } else {
43464                     item = me.add(item);
43465                 }
43466                 results.push(item);
43467             }
43468             // Resume Layouts now that all items have been added and do a single layout for all the items just added
43469             me.suspendLayout = false;
43470             me.doLayout();
43471             return results;
43472         }
43473
43474         cmp = me.prepareItems(args[0], true)[0];
43475
43476         // Floating Components are not added into the items collection
43477         // But they do get an upward ownerCt link so that they can traverse
43478         // up to their z-index parent.
43479         if (cmp.floating) {
43480             cmp.onAdded(me, index);
43481         } else {
43482             index = (index !== -1) ? index : me.items.length;
43483             if (me.fireEvent('beforeadd', me, cmp, index) !== false && me.onBeforeAdd(cmp) !== false) {
43484                 me.items.insert(index, cmp);
43485                 cmp.onAdded(me, index);
43486                 me.onAdd(cmp, index);
43487                 me.fireEvent('add', me, cmp, index);
43488             }
43489             me.doLayout();
43490         }
43491         return cmp;
43492     },
43493
43494     onAdd : Ext.emptyFn,
43495     onRemove : Ext.emptyFn,
43496
43497     /**
43498      * Inserts a Component into this Container at a specified index. Fires the
43499      * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
43500      * Component has been inserted.
43501      * @param {Number} index The index at which the Component will be inserted
43502      * into the Container's items collection
43503      * @param {Ext.Component} component The child Component to insert.<br><br>
43504      * Ext uses lazy rendering, and will only render the inserted Component should
43505      * it become necessary.<br><br>
43506      * A Component config object may be passed in order to avoid the overhead of
43507      * constructing a real Component object if lazy rendering might mean that the
43508      * inserted Component will not be rendered immediately. To take advantage of
43509      * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
43510      * property to the registered type of the Component wanted.<br><br>
43511      * For a list of all available xtypes, see {@link Ext.Component}.
43512      * @return {Ext.Component} component The Component (or config object) that was
43513      * inserted with the Container's default config values applied.
43514      */
43515     insert : function(index, comp) {
43516         return this.add(index, comp);
43517     },
43518
43519     /**
43520      * Moves a Component within the Container
43521      * @param {Number} fromIdx The index the Component you wish to move is currently at.
43522      * @param {Number} toIdx The new index for the Component.
43523      * @return {Ext.Component} component The Component (or config object) that was moved.
43524      */
43525     move : function(fromIdx, toIdx) {
43526         var items = this.items,
43527             item;
43528         item = items.removeAt(fromIdx);
43529         if (item === false) {
43530             return false;
43531         }
43532         items.insert(toIdx, item);
43533         this.doLayout();
43534         return item;
43535     },
43536
43537     // @private
43538     onBeforeAdd : function(item) {
43539         var me = this;
43540
43541         if (item.ownerCt) {
43542             item.ownerCt.remove(item, false);
43543         }
43544
43545         if (me.border === false || me.border === 0) {
43546             item.border = (item.border === true);
43547         }
43548     },
43549
43550     /**
43551      * Removes a component from this container.  Fires the {@link #beforeremove} event before removing, then fires
43552      * the {@link #remove} event after the component has been removed.
43553      * @param {Ext.Component/String} component The component reference or id to remove.
43554      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
43555      * Defaults to the value of this Container's {@link #autoDestroy} config.
43556      * @return {Ext.Component} component The Component that was removed.
43557      */
43558     remove : function(comp, autoDestroy) {
43559         var me = this,
43560             c = me.getComponent(comp);
43561
43562         if (c && me.fireEvent('beforeremove', me, c) !== false) {
43563             me.doRemove(c, autoDestroy);
43564             me.fireEvent('remove', me, c);
43565         }
43566
43567         return c;
43568     },
43569
43570     // @private
43571     doRemove : function(component, autoDestroy) {
43572         var me = this,
43573             layout = me.layout,
43574             hasLayout = layout && me.rendered;
43575
43576         me.items.remove(component);
43577         component.onRemoved();
43578
43579         if (hasLayout) {
43580             layout.onRemove(component);
43581         }
43582
43583         me.onRemove(component, autoDestroy);
43584
43585         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
43586             component.destroy();
43587         }
43588
43589         if (hasLayout && !autoDestroy) {
43590             layout.afterRemove(component);
43591         }
43592
43593         if (!me.destroying) {
43594             me.doLayout();
43595         }
43596     },
43597
43598     /**
43599      * Removes all components from this container.
43600      * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
43601      * Defaults to the value of this Container's {@link #autoDestroy} config.
43602      * @return {Ext.Component[]} Array of the destroyed components
43603      */
43604     removeAll : function(autoDestroy) {
43605         var me = this,
43606             removeItems = me.items.items.slice(),
43607             items = [],
43608             i = 0,
43609             len = removeItems.length,
43610             item;
43611
43612         // Suspend Layouts while we remove multiple items from the container
43613         me.suspendLayout = true;
43614         for (; i < len; i++) {
43615             item = removeItems[i];
43616             me.remove(item, autoDestroy);
43617
43618             if (item.ownerCt !== me) {
43619                 items.push(item);
43620             }
43621         }
43622
43623         // Resume Layouts now that all items have been removed and do a single layout (if we removed anything!)
43624         me.suspendLayout = false;
43625         if (len) {
43626             me.doLayout();
43627         }
43628         return items;
43629     },
43630
43631     // Used by ComponentQuery to retrieve all of the items
43632     // which can potentially be considered a child of this Container.
43633     // This should be overriden by components which have child items
43634     // that are not contained in items. For example dockedItems, menu, etc
43635     // IMPORTANT note for maintainers:
43636     //  Items are returned in tree traversal order. Each item is appended to the result array
43637     //  followed by the results of that child's getRefItems call.
43638     //  Floating child items are appended after internal child items.
43639     getRefItems : function(deep) {
43640         var me = this,
43641             items = me.items.items,
43642             len = items.length,
43643             i = 0,
43644             item,
43645             result = [];
43646
43647         for (; i < len; i++) {
43648             item = items[i];
43649             result.push(item);
43650             if (deep && item.getRefItems) {
43651                 result.push.apply(result, item.getRefItems(true));
43652             }
43653         }
43654
43655         // Append floating items to the list.
43656         // These will only be present after they are rendered.
43657         if (me.floatingItems && me.floatingItems.accessList) {
43658             result.push.apply(result, me.floatingItems.accessList);
43659         }
43660
43661         return result;
43662     },
43663
43664     /**
43665      * Cascades down the component/container heirarchy from this component (passed in the first call), calling the specified function with
43666      * each component. The scope (<code>this</code> reference) of the
43667      * function call will be the scope provided or the current component. The arguments to the function
43668      * will be the args provided or the current component. If the function returns false at any point,
43669      * the cascade is stopped on that branch.
43670      * @param {Function} fn The function to call
43671      * @param {Object} [scope] The scope of the function (defaults to current component)
43672      * @param {Array} [args] The args to call the function with. The current component always passed as the last argument.
43673      * @return {Ext.Container} this
43674      */
43675     cascade : function(fn, scope, origArgs){
43676         var me = this,
43677             cs = me.items ? me.items.items : [],
43678             len = cs.length,
43679             i = 0,
43680             c,
43681             args = origArgs ? origArgs.concat(me) : [me],
43682             componentIndex = args.length - 1;
43683
43684         if (fn.apply(scope || me, args) !== false) {
43685             for(; i < len; i++){
43686                 c = cs[i];
43687                 if (c.cascade) {
43688                     c.cascade(fn, scope, origArgs);
43689                 } else {
43690                     args[componentIndex] = c;
43691                     fn.apply(scope || cs, args);
43692                 }
43693             }
43694         }
43695         return this;
43696     },
43697
43698     /**
43699      * Examines this container's <code>{@link #items}</code> <b>property</b>
43700      * and gets a direct child component of this container.
43701      * @param {String/Number} comp This parameter may be any of the following:
43702      * <div><ul class="mdetail-params">
43703      * <li>a <b><code>String</code></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
43704      * or <code>{@link Ext.Component#id id}</code> of the child component </li>
43705      * <li>a <b><code>Number</code></b> : representing the position of the child component
43706      * within the <code>{@link #items}</code> <b>property</b></li>
43707      * </ul></div>
43708      * <p>For additional information see {@link Ext.util.MixedCollection#get}.
43709      * @return Ext.Component The component (if found).
43710      */
43711     getComponent : function(comp) {
43712         if (Ext.isObject(comp)) {
43713             comp = comp.getItemId();
43714         }
43715
43716         return this.items.get(comp);
43717     },
43718
43719     /**
43720      * Retrieves all descendant components which match the passed selector.
43721      * Executes an Ext.ComponentQuery.query using this container as its root.
43722      * @param {String} selector (optional) Selector complying to an Ext.ComponentQuery selector.
43723      * If no selector is specified all items will be returned.
43724      * @return {Ext.Component[]} Components which matched the selector
43725      */
43726     query : function(selector) {
43727         selector = selector || '*';
43728         return Ext.ComponentQuery.query(selector, this);
43729     },
43730
43731     /**
43732      * Retrieves the first direct child of this container which matches the passed selector.
43733      * The passed in selector must comply with an Ext.ComponentQuery selector.
43734      * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
43735      * specified, the first child will be returned.
43736      * @return Ext.Component
43737      */
43738     child : function(selector) {
43739         selector = selector || '';
43740         return this.query('> ' + selector)[0] || null;
43741     },
43742
43743     /**
43744      * Retrieves the first descendant of this container which matches the passed selector.
43745      * The passed in selector must comply with an Ext.ComponentQuery selector.
43746      * @param {String} selector (optional) An Ext.ComponentQuery selector. If no selector is
43747      * specified, the first child will be returned.
43748      * @return Ext.Component
43749      */
43750     down : function(selector) {
43751         return this.query(selector)[0] || null;
43752     },
43753
43754     // inherit docs
43755     show : function() {
43756         this.callParent(arguments);
43757         this.performDeferredLayouts();
43758         return this;
43759     },
43760
43761     // Lay out any descendant containers who queued a layout operation during the time this was hidden
43762     // This is also called by Panel after it expands because descendants of a collapsed Panel allso queue any layout ops.
43763     performDeferredLayouts: function() {
43764         var layoutCollection = this.layoutOnShow,
43765             ln = layoutCollection.getCount(),
43766             i = 0,
43767             needsLayout,
43768             item;
43769
43770         for (; i < ln; i++) {
43771             item = layoutCollection.get(i);
43772             needsLayout = item.needsLayout;
43773
43774             if (Ext.isObject(needsLayout)) {
43775                 item.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
43776             }
43777         }
43778         layoutCollection.clear();
43779     },
43780
43781     //@private
43782     // Enable all immediate children that was previously disabled
43783     onEnable: function() {
43784         Ext.Array.each(this.query('[isFormField]'), function(item) {
43785             if (item.resetDisable) {
43786                 item.enable();
43787                 delete item.resetDisable;
43788             }
43789         });
43790         this.callParent();
43791     },
43792
43793     // @private
43794     // Disable all immediate children that was previously disabled
43795     onDisable: function() {
43796         Ext.Array.each(this.query('[isFormField]'), function(item) {
43797             if (item.resetDisable !== false && !item.disabled) {
43798                 item.disable();
43799                 item.resetDisable = true;
43800             }
43801         });
43802         this.callParent();
43803     },
43804
43805     /**
43806      * Occurs before componentLayout is run. Returning false from this method will prevent the containerLayout
43807      * from being executed.
43808      */
43809     beforeLayout: function() {
43810         return true;
43811     },
43812
43813     // @private
43814     beforeDestroy : function() {
43815         var me = this,
43816             items = me.items,
43817             c;
43818
43819         if (items) {
43820             while ((c = items.first())) {
43821                 me.doRemove(c, true);
43822             }
43823         }
43824
43825         Ext.destroy(
43826             me.layout
43827         );
43828         me.callParent();
43829     }
43830 });
43831
43832 /**
43833  * Base class for any Ext.Component that may contain other Components. Containers handle the basic behavior of
43834  * containing items, namely adding, inserting and removing items.
43835  *
43836  * The most commonly used Container classes are Ext.panel.Panel, Ext.window.Window and
43837  * Ext.tab.Panel. If you do not need the capabilities offered by the aforementioned classes you can create a
43838  * lightweight Container to be encapsulated by an HTML element to your specifications by using the
43839  * {@link Ext.Component#autoEl autoEl} config option.
43840  *
43841  * The code below illustrates how to explicitly create a Container:
43842  *
43843  *     @example
43844  *     // Explicitly create a Container
43845  *     Ext.create('Ext.container.Container', {
43846  *         layout: {
43847  *             type: 'hbox'
43848  *         },
43849  *         width: 400,
43850  *         renderTo: Ext.getBody(),
43851  *         border: 1,
43852  *         style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
43853  *         defaults: {
43854  *             labelWidth: 80,
43855  *             // implicitly create Container by specifying xtype
43856  *             xtype: 'datefield',
43857  *             flex: 1,
43858  *             style: {
43859  *                 padding: '10px'
43860  *             }
43861  *         },
43862  *         items: [{
43863  *             xtype: 'datefield',
43864  *             name: 'startDate',
43865  *             fieldLabel: 'Start date'
43866  *         },{
43867  *             xtype: 'datefield',
43868  *             name: 'endDate',
43869  *             fieldLabel: 'End date'
43870  *         }]
43871  *     });
43872  *
43873  * ## Layout
43874  *
43875  * Container classes delegate the rendering of child Components to a layout manager class which must be configured into
43876  * the Container using the `{@link #layout}` configuration property.
43877  *
43878  * When either specifying child `{@link #items}` of a Container, or dynamically {@link #add adding} Components to a
43879  * Container, remember to consider how you wish the Container to arrange those child elements, and whether those child
43880  * elements need to be sized using one of Ext's built-in `{@link #layout}` schemes. By default, Containers use the
43881  * {@link Ext.layout.container.Auto Auto} scheme which only renders child components, appending them one after the other
43882  * inside the Container, and **does not apply any sizing** at all.
43883  *
43884  * A common mistake is when a developer neglects to specify a `{@link #layout}` (e.g. widgets like GridPanels or
43885  * TreePanels are added to Containers for which no `{@link #layout}` has been specified). If a Container is left to
43886  * use the default {@link Ext.layout.container.Auto Auto} scheme, none of its child components will be resized, or changed in
43887  * any way when the Container is resized.
43888  *
43889  * Certain layout managers allow dynamic addition of child components. Those that do include
43890  * Ext.layout.container.Card, Ext.layout.container.Anchor, Ext.layout.container.VBox,
43891  * Ext.layout.container.HBox, and Ext.layout.container.Table. For example:
43892  *
43893  *     //  Create the GridPanel.
43894  *     var myNewGrid = new Ext.grid.Panel({
43895  *         store: myStore,
43896  *         headers: myHeaders,
43897  *         title: 'Results', // the title becomes the title of the tab
43898  *     });
43899  *
43900  *     myTabPanel.add(myNewGrid); // {@link Ext.tab.Panel} implicitly uses {@link Ext.layout.container.Card Card}
43901  *     myTabPanel.{@link Ext.tab.Panel#setActiveTab setActiveTab}(myNewGrid);
43902  *
43903  * The example above adds a newly created GridPanel to a TabPanel. Note that a TabPanel uses {@link
43904  * Ext.layout.container.Card} as its layout manager which means all its child items are sized to {@link
43905  * Ext.layout.container.Fit fit} exactly into its client area.
43906  *
43907  * **_Overnesting is a common problem_**. An example of overnesting occurs when a GridPanel is added to a TabPanel by
43908  * wrapping the GridPanel _inside_ a wrapping Panel (that has no `{@link #layout}` specified) and then add that
43909  * wrapping Panel to the TabPanel. The point to realize is that a GridPanel **is** a Component which can be added
43910  * directly to a Container. If the wrapping Panel has no `{@link #layout}` configuration, then the overnested
43911  * GridPanel will not be sized as expected.
43912  *
43913  * ## Adding via remote configuration
43914  *
43915  * A server side script can be used to add Components which are generated dynamically on the server. An example of
43916  * adding a GridPanel to a TabPanel where the GridPanel is generated by the server based on certain parameters:
43917  *
43918  *     // execute an Ajax request to invoke server side script:
43919  *     Ext.Ajax.request({
43920  *         url: 'gen-invoice-grid.php',
43921  *         // send additional parameters to instruct server script
43922  *         params: {
43923  *             startDate: Ext.getCmp('start-date').getValue(),
43924  *             endDate: Ext.getCmp('end-date').getValue()
43925  *         },
43926  *         // process the response object to add it to the TabPanel:
43927  *         success: function(xhr) {
43928  *             var newComponent = eval(xhr.responseText); // see discussion below
43929  *             myTabPanel.add(newComponent); // add the component to the TabPanel
43930  *             myTabPanel.setActiveTab(newComponent);
43931  *         },
43932  *         failure: function() {
43933  *             Ext.Msg.alert("Grid create failed", "Server communication failure");
43934  *         }
43935  *     });
43936  *
43937  * The server script needs to return a JSON representation of a configuration object, which, when decoded will return a
43938  * config object with an {@link Ext.Component#xtype xtype}. The server might return the following JSON:
43939  *
43940  *     {
43941  *         "xtype": 'grid',
43942  *         "title": 'Invoice Report',
43943  *         "store": {
43944  *             "model": 'Invoice',
43945  *             "proxy": {
43946  *                 "type": 'ajax',
43947  *                 "url": 'get-invoice-data.php',
43948  *                 "reader": {
43949  *                     "type": 'json'
43950  *                     "record": 'transaction',
43951  *                     "idProperty": 'id',
43952  *                     "totalRecords": 'total'
43953  *                 })
43954  *             },
43955  *             "autoLoad": {
43956  *                 "params": {
43957  *                     "startDate": '01/01/2008',
43958  *                     "endDate": '01/31/2008'
43959  *                 }
43960  *             }
43961  *         },
43962  *         "headers": [
43963  *             {"header": "Customer", "width": 250, "dataIndex": 'customer', "sortable": true},
43964  *             {"header": "Invoice Number", "width": 120, "dataIndex": 'invNo', "sortable": true},
43965  *             {"header": "Invoice Date", "width": 100, "dataIndex": 'date', "renderer": Ext.util.Format.dateRenderer('M d, y'), "sortable": true},
43966  *             {"header": "Value", "width": 120, "dataIndex": 'value', "renderer": 'usMoney', "sortable": true}
43967  *         ]
43968  *     }
43969  *
43970  * When the above code fragment is passed through the `eval` function in the success handler of the Ajax request, the
43971  * result will be a config object which, when added to a Container, will cause instantiation of a GridPanel. **Be sure
43972  * that the Container is configured with a layout which sizes and positions the child items to your requirements.**
43973  *
43974  * **Note:** since the code above is _generated_ by a server script, the `autoLoad` params for the Store, the user's
43975  * preferred date format, the metadata to allow generation of the Model layout, and the ColumnModel can all be generated
43976  * into the code since these are all known on the server.
43977  */
43978 Ext.define('Ext.container.Container', {
43979     extend: 'Ext.container.AbstractContainer',
43980     alias: 'widget.container',
43981     alternateClassName: 'Ext.Container',
43982
43983     /**
43984      * Return the immediate child Component in which the passed element is located.
43985      * @param {Ext.Element/HTMLElement/String} el The element to test (or ID of element).
43986      * @return {Ext.Component} The child item which contains the passed element.
43987      */
43988     getChildByElement: function(el) {
43989         var item,
43990             itemEl,
43991             i = 0,
43992             it = this.items.items,
43993             ln = it.length;
43994
43995         el = Ext.getDom(el);
43996         for (; i < ln; i++) {
43997             item = it[i];
43998             itemEl = item.getEl();
43999             if ((itemEl.dom === el) || itemEl.contains(el)) {
44000                 return item;
44001             }
44002         }
44003         return null;
44004     }
44005 });
44006
44007 /**
44008  * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using
44009  * the right-justified button container.
44010  *
44011  *     @example
44012  *     Ext.create('Ext.panel.Panel', {
44013  *          title: 'Toolbar Fill Example',
44014  *          width: 300,
44015  *          height: 200,
44016  *          tbar : [
44017  *              'Item 1',
44018  *              { xtype: 'tbfill' },
44019  *              'Item 2'
44020  *          ],
44021  *          renderTo: Ext.getBody()
44022  *      });
44023  */
44024 Ext.define('Ext.toolbar.Fill', {
44025     extend: 'Ext.Component',
44026     alias: 'widget.tbfill',
44027     alternateClassName: 'Ext.Toolbar.Fill',
44028     isFill : true,
44029     flex: 1
44030 });
44031 /**
44032  * @class Ext.toolbar.Item
44033  * @extends Ext.Component
44034  * The base class that other non-interacting Toolbar Item classes should extend in order to
44035  * get some basic common toolbar item functionality.
44036  */
44037 Ext.define('Ext.toolbar.Item', {
44038     extend: 'Ext.Component',
44039     alias: 'widget.tbitem',
44040     alternateClassName: 'Ext.Toolbar.Item',
44041     enable:Ext.emptyFn,
44042     disable:Ext.emptyFn,
44043     focus:Ext.emptyFn
44044     /**
44045      * @cfg {String} overflowText Text to be used for the menu if the item is overflowed.
44046      */
44047 });
44048 /**
44049  * @class Ext.toolbar.Separator
44050  * @extends Ext.toolbar.Item
44051  * A simple class that adds a vertical separator bar between toolbar items (css class: 'x-toolbar-separator').
44052  *
44053  *     @example
44054  *     Ext.create('Ext.panel.Panel', {
44055  *         title: 'Toolbar Seperator Example',
44056  *         width: 300,
44057  *         height: 200,
44058  *         tbar : [
44059  *             'Item 1',
44060  *             { xtype: 'tbseparator' },
44061  *             'Item 2'
44062  *         ],
44063  *         renderTo: Ext.getBody()
44064  *     });
44065  */
44066 Ext.define('Ext.toolbar.Separator', {
44067     extend: 'Ext.toolbar.Item',
44068     alias: 'widget.tbseparator',
44069     alternateClassName: 'Ext.Toolbar.Separator',
44070     baseCls: Ext.baseCSSPrefix + 'toolbar-separator',
44071     focusable: false
44072 });
44073 /**
44074  * @class Ext.menu.Manager
44075  * Provides a common registry of all menus on a page.
44076  * @singleton
44077  */
44078 Ext.define('Ext.menu.Manager', {
44079     singleton: true,
44080     requires: [
44081         'Ext.util.MixedCollection',
44082         'Ext.util.KeyMap'
44083     ],
44084     alternateClassName: 'Ext.menu.MenuMgr',
44085
44086     uses: ['Ext.menu.Menu'],
44087
44088     menus: {},
44089     groups: {},
44090     attached: false,
44091     lastShow: new Date(),
44092
44093     init: function() {
44094         var me = this;
44095         
44096         me.active = Ext.create('Ext.util.MixedCollection');
44097         Ext.getDoc().addKeyListener(27, function() {
44098             if (me.active.length > 0) {
44099                 me.hideAll();
44100             }
44101         }, me);
44102     },
44103
44104     /**
44105      * Hides all menus that are currently visible
44106      * @return {Boolean} success True if any active menus were hidden.
44107      */
44108     hideAll: function() {
44109         var active = this.active,
44110             c;
44111         if (active && active.length > 0) {
44112             c = active.clone();
44113             c.each(function(m) {
44114                 m.hide();
44115             });
44116             return true;
44117         }
44118         return false;
44119     },
44120
44121     onHide: function(m) {
44122         var me = this,
44123             active = me.active;
44124         active.remove(m);
44125         if (active.length < 1) {
44126             Ext.getDoc().un('mousedown', me.onMouseDown, me);
44127             me.attached = false;
44128         }
44129     },
44130
44131     onShow: function(m) {
44132         var me = this,
44133             active   = me.active,
44134             last     = active.last(),
44135             attached = me.attached,
44136             menuEl   = m.getEl(),
44137             zIndex;
44138
44139         me.lastShow = new Date();
44140         active.add(m);
44141         if (!attached) {
44142             Ext.getDoc().on('mousedown', me.onMouseDown, me);
44143             me.attached = true;
44144         }
44145         m.toFront();
44146     },
44147
44148     onBeforeHide: function(m) {
44149         if (m.activeChild) {
44150             m.activeChild.hide();
44151         }
44152         if (m.autoHideTimer) {
44153             clearTimeout(m.autoHideTimer);
44154             delete m.autoHideTimer;
44155         }
44156     },
44157
44158     onBeforeShow: function(m) {
44159         var active = this.active,
44160             parentMenu = m.parentMenu;
44161             
44162         active.remove(m);
44163         if (!parentMenu && !m.allowOtherMenus) {
44164             this.hideAll();
44165         }
44166         else if (parentMenu && parentMenu.activeChild && m != parentMenu.activeChild) {
44167             parentMenu.activeChild.hide();
44168         }
44169     },
44170
44171     // private
44172     onMouseDown: function(e) {
44173         var me = this,
44174             active = me.active,
44175             lastShow = me.lastShow,
44176             target = e.target;
44177
44178         if (Ext.Date.getElapsed(lastShow) > 50 && active.length > 0 && !e.getTarget('.' + Ext.baseCSSPrefix + 'menu')) {
44179             me.hideAll();
44180             // in IE, if we mousedown on a focusable element, the focus gets cancelled and the focus event is never
44181             // fired on the element, so we'll focus it here
44182             if (Ext.isIE && Ext.fly(target).focusable()) {
44183                 target.focus();
44184             }
44185         }
44186     },
44187
44188     // private
44189     register: function(menu) {
44190         var me = this;
44191
44192         if (!me.active) {
44193             me.init();
44194         }
44195
44196         if (menu.floating) {
44197             me.menus[menu.id] = menu;
44198             menu.on({
44199                 beforehide: me.onBeforeHide,
44200                 hide: me.onHide,
44201                 beforeshow: me.onBeforeShow,
44202                 show: me.onShow,
44203                 scope: me
44204             });
44205         }
44206     },
44207
44208     /**
44209      * Returns a {@link Ext.menu.Menu} object
44210      * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
44211      * be used to generate and return a new Menu this.
44212      * @return {Ext.menu.Menu} The specified menu, or null if none are found
44213      */
44214     get: function(menu) {
44215         var menus = this.menus;
44216         
44217         if (typeof menu == 'string') { // menu id
44218             if (!menus) {  // not initialized, no menus to return
44219                 return null;
44220             }
44221             return menus[menu];
44222         } else if (menu.isMenu) {  // menu instance
44223             return menu;
44224         } else if (Ext.isArray(menu)) { // array of menu items
44225             return Ext.create('Ext.menu.Menu', {items:menu});
44226         } else { // otherwise, must be a config
44227             return Ext.ComponentManager.create(menu, 'menu');
44228         }
44229     },
44230
44231     // private
44232     unregister: function(menu) {
44233         var me = this,
44234             menus = me.menus,
44235             active = me.active;
44236
44237         delete menus[menu.id];
44238         active.remove(menu);
44239         menu.un({
44240             beforehide: me.onBeforeHide,
44241             hide: me.onHide,
44242             beforeshow: me.onBeforeShow,
44243             show: me.onShow,
44244             scope: me
44245         });
44246     },
44247
44248     // private
44249     registerCheckable: function(menuItem) {
44250         var groups  = this.groups,
44251             groupId = menuItem.group;
44252
44253         if (groupId) {
44254             if (!groups[groupId]) {
44255                 groups[groupId] = [];
44256             }
44257
44258             groups[groupId].push(menuItem);
44259         }
44260     },
44261
44262     // private
44263     unregisterCheckable: function(menuItem) {
44264         var groups  = this.groups,
44265             groupId = menuItem.group;
44266
44267         if (groupId) {
44268             Ext.Array.remove(groups[groupId], menuItem);
44269         }
44270     },
44271
44272     onCheckChange: function(menuItem, state) {
44273         var groups  = this.groups,
44274             groupId = menuItem.group,
44275             i       = 0,
44276             group, ln, curr;
44277
44278         if (groupId && state) {
44279             group = groups[groupId];
44280             ln = group.length;
44281             for (; i < ln; i++) {
44282                 curr = group[i];
44283                 if (curr != menuItem) {
44284                     curr.setChecked(false);
44285                 }
44286             }
44287         }
44288     }
44289 });
44290 /**
44291  * Component layout for buttons
44292  * @class Ext.layout.component.Button
44293  * @extends Ext.layout.component.Component
44294  * @private
44295  */
44296 Ext.define('Ext.layout.component.Button', {
44297
44298     /* Begin Definitions */
44299
44300     alias: ['layout.button'],
44301
44302     extend: 'Ext.layout.component.Component',
44303
44304     /* End Definitions */
44305
44306     type: 'button',
44307
44308     cellClsRE: /-btn-(tl|br)\b/,
44309     htmlRE: /<.*>/,
44310
44311     beforeLayout: function() {
44312         return this.callParent(arguments) || this.lastText !== this.owner.text;
44313     },
44314
44315     /**
44316      * Set the dimensions of the inner &lt;button&gt; element to match the
44317      * component dimensions.
44318      */
44319     onLayout: function(width, height) {
44320         var me = this,
44321             isNum = Ext.isNumber,
44322             owner = me.owner,
44323             ownerEl = owner.el,
44324             btnEl = owner.btnEl,
44325             btnInnerEl = owner.btnInnerEl,
44326             btnIconEl = owner.btnIconEl,
44327             sizeIconEl = (owner.icon || owner.iconCls) && (owner.iconAlign == "top" || owner.iconAlign == "bottom"),
44328             minWidth = owner.minWidth,
44329             maxWidth = owner.maxWidth,
44330             ownerWidth, btnFrameWidth, metrics;
44331
44332         me.getTargetInfo();
44333         me.callParent(arguments);
44334
44335         btnInnerEl.unclip();
44336         me.setTargetSize(width, height);
44337
44338         if (!isNum(width)) {
44339             // In IE7 strict mode button elements with width:auto get strange extra side margins within
44340             // the wrapping table cell, but they go away if the width is explicitly set. So we measure
44341             // the size of the text and set the width to match.
44342             if (owner.text && (Ext.isIE6 || Ext.isIE7) && Ext.isStrict && btnEl && btnEl.getWidth() > 20) {
44343                 btnFrameWidth = me.btnFrameWidth;
44344                 metrics = Ext.util.TextMetrics.measure(btnInnerEl, owner.text);
44345                 ownerEl.setWidth(metrics.width + btnFrameWidth + me.adjWidth);
44346                 btnEl.setWidth(metrics.width + btnFrameWidth);
44347                 btnInnerEl.setWidth(metrics.width + btnFrameWidth);
44348
44349                 if (sizeIconEl) {
44350                     btnIconEl.setWidth(metrics.width + btnFrameWidth);
44351                 }
44352             } else {
44353                 // Remove any previous fixed widths
44354                 ownerEl.setWidth(null);
44355                 btnEl.setWidth(null);
44356                 btnInnerEl.setWidth(null);
44357                 btnIconEl.setWidth(null);
44358             }
44359
44360             // Handle maxWidth/minWidth config
44361             if (minWidth || maxWidth) {
44362                 ownerWidth = ownerEl.getWidth();
44363                 if (minWidth && (ownerWidth < minWidth)) {
44364                     me.setTargetSize(minWidth, height);
44365                 }
44366                 else if (maxWidth && (ownerWidth > maxWidth)) {
44367                     btnInnerEl.clip();
44368                     me.setTargetSize(maxWidth, height);
44369                 }
44370             }
44371         }
44372
44373         this.lastText = owner.text;
44374     },
44375
44376     setTargetSize: function(width, height) {
44377         var me = this,
44378             owner = me.owner,
44379             isNum = Ext.isNumber,
44380             btnInnerEl = owner.btnInnerEl,
44381             btnWidth = (isNum(width) ? width - me.adjWidth : width),
44382             btnHeight = (isNum(height) ? height - me.adjHeight : height),
44383             btnFrameHeight = me.btnFrameHeight,
44384             text = owner.getText(),
44385             textHeight;
44386
44387         me.callParent(arguments);
44388         me.setElementSize(owner.btnEl, btnWidth, btnHeight);
44389         me.setElementSize(btnInnerEl, btnWidth, btnHeight);
44390         if (btnHeight >= 0) {
44391             btnInnerEl.setStyle('line-height', btnHeight - btnFrameHeight + 'px');
44392         }
44393
44394         // Button text may contain markup that would force it to wrap to more than one line (e.g. 'Button<br>Label').
44395         // When this happens, we cannot use the line-height set above for vertical centering; we instead reset the
44396         // line-height to normal, measure the rendered text height, and add padding-top to center the text block
44397         // vertically within the button's height. This is more expensive than the basic line-height approach so
44398         // we only do it if the text contains markup.
44399         if (text && this.htmlRE.test(text)) {
44400             btnInnerEl.setStyle('line-height', 'normal');
44401             textHeight = Ext.util.TextMetrics.measure(btnInnerEl, text).height;
44402             btnInnerEl.setStyle('padding-top', me.btnFrameTop + Math.max(btnInnerEl.getHeight() - btnFrameHeight - textHeight, 0) / 2 + 'px');
44403             me.setElementSize(btnInnerEl, btnWidth, btnHeight);
44404         }
44405     },
44406
44407     getTargetInfo: function() {
44408         var me = this,
44409             owner = me.owner,
44410             ownerEl = owner.el,
44411             frameSize = me.frameSize,
44412             frameBody = owner.frameBody,
44413             btnWrap = owner.btnWrap,
44414             innerEl = owner.btnInnerEl;
44415
44416         if (!('adjWidth' in me)) {
44417             Ext.apply(me, {
44418                 // Width adjustment must take into account the arrow area. The btnWrap is the <em> which has padding to accommodate the arrow.
44419                 adjWidth: frameSize.left + frameSize.right + ownerEl.getBorderWidth('lr') + ownerEl.getPadding('lr') +
44420                           btnWrap.getPadding('lr') + (frameBody ? frameBody.getFrameWidth('lr') : 0),
44421                 adjHeight: frameSize.top + frameSize.bottom + ownerEl.getBorderWidth('tb') + ownerEl.getPadding('tb') +
44422                            btnWrap.getPadding('tb') + (frameBody ? frameBody.getFrameWidth('tb') : 0),
44423                 btnFrameWidth: innerEl.getFrameWidth('lr'),
44424                 btnFrameHeight: innerEl.getFrameWidth('tb'),
44425                 btnFrameTop: innerEl.getFrameWidth('t')
44426             });
44427         }
44428
44429         return me.callParent();
44430     }
44431 });
44432 /**
44433  * @docauthor Robert Dougan <rob@sencha.com>
44434  *
44435  * Create simple buttons with this component. Customisations include {@link #iconAlign aligned}
44436  * {@link #iconCls icons}, {@link #menu dropdown menus}, {@link #tooltip tooltips}
44437  * and {@link #scale sizing options}. Specify a {@link #handler handler} to run code when
44438  * a user clicks the button, or use {@link #listeners listeners} for other events such as
44439  * {@link #mouseover mouseover}. Example usage:
44440  *
44441  *     @example
44442  *     Ext.create('Ext.Button', {
44443  *         text: 'Click me',
44444  *         renderTo: Ext.getBody(),
44445  *         handler: function() {
44446  *             alert('You clicked the button!')
44447  *         }
44448  *     });
44449  *
44450  * The {@link #handler} configuration can also be updated dynamically using the {@link #setHandler}
44451  * method.  Example usage:
44452  *
44453  *     @example
44454  *     Ext.create('Ext.Button', {
44455  *         text    : 'Dynamic Handler Button',
44456  *         renderTo: Ext.getBody(),
44457  *         handler : function() {
44458  *             // this button will spit out a different number every time you click it.
44459  *             // so firstly we must check if that number is already set:
44460  *             if (this.clickCount) {
44461  *                 // looks like the property is already set, so lets just add 1 to that number and alert the user
44462  *                 this.clickCount++;
44463  *                 alert('You have clicked the button "' + this.clickCount + '" times.\n\nTry clicking it again..');
44464  *             } else {
44465  *                 // if the clickCount property is not set, we will set it and alert the user
44466  *                 this.clickCount = 1;
44467  *                 alert('You just clicked the button for the first time!\n\nTry pressing it again..');
44468  *             }
44469  *         }
44470  *     });
44471  *
44472  * A button within a container:
44473  *
44474  *     @example
44475  *     Ext.create('Ext.Container', {
44476  *         renderTo: Ext.getBody(),
44477  *         items   : [
44478  *             {
44479  *                 xtype: 'button',
44480  *                 text : 'My Button'
44481  *             }
44482  *         ]
44483  *     });
44484  *
44485  * A useful option of Button is the {@link #scale} configuration. This configuration has three different options:
44486  *
44487  * - `'small'`
44488  * - `'medium'`
44489  * - `'large'`
44490  *
44491  * Example usage:
44492  *
44493  *     @example
44494  *     Ext.create('Ext.Button', {
44495  *         renderTo: document.body,
44496  *         text    : 'Click me',
44497  *         scale   : 'large'
44498  *     });
44499  *
44500  * Buttons can also be toggled. To enable this, you simple set the {@link #enableToggle} property to `true`.
44501  * Example usage:
44502  *
44503  *     @example
44504  *     Ext.create('Ext.Button', {
44505  *         renderTo: Ext.getBody(),
44506  *         text: 'Click Me',
44507  *         enableToggle: true
44508  *     });
44509  *
44510  * You can assign a menu to a button by using the {@link #menu} configuration. This standard configuration
44511  * can either be a reference to a {@link Ext.menu.Menu menu} object, a {@link Ext.menu.Menu menu} id or a
44512  * {@link Ext.menu.Menu menu} config blob. When assigning a menu to a button, an arrow is automatically
44513  * added to the button.  You can change the alignment of the arrow using the {@link #arrowAlign} configuration
44514  * on button.  Example usage:
44515  *
44516  *     @example
44517  *     Ext.create('Ext.Button', {
44518  *         text      : 'Menu button',
44519  *         renderTo  : Ext.getBody(),
44520  *         arrowAlign: 'bottom',
44521  *         menu      : [
44522  *             {text: 'Item 1'},
44523  *             {text: 'Item 2'},
44524  *             {text: 'Item 3'},
44525  *             {text: 'Item 4'}
44526  *         ]
44527  *     });
44528  *
44529  * Using listeners, you can easily listen to events fired by any component, using the {@link #listeners}
44530  * configuration or using the {@link #addListener} method.  Button has a variety of different listeners:
44531  *
44532  * - `click`
44533  * - `toggle`
44534  * - `mouseover`
44535  * - `mouseout`
44536  * - `mouseshow`
44537  * - `menuhide`
44538  * - `menutriggerover`
44539  * - `menutriggerout`
44540  *
44541  * Example usage:
44542  *
44543  *     @example
44544  *     Ext.create('Ext.Button', {
44545  *         text     : 'Button',
44546  *         renderTo : Ext.getBody(),
44547  *         listeners: {
44548  *             click: function() {
44549  *                 // this == the button, as we are in the local scope
44550  *                 this.setText('I was clicked!');
44551  *             },
44552  *             mouseover: function() {
44553  *                 // set a new config which says we moused over, if not already set
44554  *                 if (!this.mousedOver) {
44555  *                     this.mousedOver = true;
44556  *                     alert('You moused over a button!\n\nI wont do this again.');
44557  *                 }
44558  *             }
44559  *         }
44560  *     });
44561  */
44562 Ext.define('Ext.button.Button', {
44563
44564     /* Begin Definitions */
44565     alias: 'widget.button',
44566     extend: 'Ext.Component',
44567
44568     requires: [
44569         'Ext.menu.Manager',
44570         'Ext.util.ClickRepeater',
44571         'Ext.layout.component.Button',
44572         'Ext.util.TextMetrics',
44573         'Ext.util.KeyMap'
44574     ],
44575
44576     alternateClassName: 'Ext.Button',
44577     /* End Definitions */
44578
44579     isButton: true,
44580     componentLayout: 'button',
44581
44582     /**
44583      * @property {Boolean} hidden
44584      * True if this button is hidden. Read-only.
44585      */
44586     hidden: false,
44587
44588     /**
44589      * @property {Boolean} disabled
44590      * True if this button is disabled. Read-only.
44591      */
44592     disabled: false,
44593
44594     /**
44595      * @property {Boolean} pressed
44596      * True if this button is pressed (only if enableToggle = true). Read-only.
44597      */
44598     pressed: false,
44599
44600     /**
44601      * @cfg {String} text
44602      * The button text to be used as innerHTML (html tags are accepted).
44603      */
44604
44605     /**
44606      * @cfg {String} icon
44607      * The path to an image to display in the button (the image will be set as the background-image CSS property of the
44608      * button by default, so if you want a mixed icon/text button, set cls:'x-btn-text-icon')
44609      */
44610
44611     /**
44612      * @cfg {Function} handler
44613      * A function called when the button is clicked (can be used instead of click event).
44614      * @cfg {Ext.button.Button} handler.button This button.
44615      * @cfg {Ext.EventObject} handler.e The click event.
44616      */
44617
44618     /**
44619      * @cfg {Number} minWidth
44620      * The minimum width for this button (used to give a set of buttons a common width).
44621      * See also {@link Ext.panel.Panel}.{@link Ext.panel.Panel#minButtonWidth minButtonWidth}.
44622      */
44623
44624     /**
44625      * @cfg {String/Object} tooltip
44626      * The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted) or
44627      * QuickTips config object.
44628      */
44629
44630     /**
44631      * @cfg {Boolean} [hidden=false]
44632      * True to start hidden.
44633      */
44634
44635     /**
44636      * @cfg {Boolean} [disabled=true]
44637      * True to start disabled.
44638      */
44639
44640     /**
44641      * @cfg {Boolean} [pressed=false]
44642      * True to start pressed (only if enableToggle = true)
44643      */
44644
44645     /**
44646      * @cfg {String} toggleGroup
44647      * The group this toggle button is a member of (only 1 per group can be pressed)
44648      */
44649
44650     /**
44651      * @cfg {Boolean/Object} [repeat=false]
44652      * True to repeat fire the click event while the mouse is down. This can also be a
44653      * {@link Ext.util.ClickRepeater ClickRepeater} config object.
44654      */
44655
44656     /**
44657      * @cfg {Number} tabIndex
44658      * Set a DOM tabIndex for this button.
44659      */
44660
44661     /**
44662      * @cfg {Boolean} [allowDepress=true]
44663      * False to not allow a pressed Button to be depressed. Only valid when {@link #enableToggle} is true.
44664      */
44665
44666     /**
44667      * @cfg {Boolean} [enableToggle=false]
44668      * True to enable pressed/not pressed toggling.
44669      */
44670     enableToggle: false,
44671
44672     /**
44673      * @cfg {Function} toggleHandler
44674      * Function called when a Button with {@link #enableToggle} set to true is clicked.
44675      * @cfg {Ext.button.Button} toggleHandler.button This button.
44676      * @cfg {Boolean} toggleHandler.state The next state of the Button, true means pressed.
44677      */
44678
44679     /**
44680      * @cfg {Ext.menu.Menu/String/Object} menu
44681      * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob.
44682      */
44683
44684     /**
44685      * @cfg {String} menuAlign
44686      * The position to align the menu to (see {@link Ext.Element#alignTo} for more details).
44687      */
44688     menuAlign: 'tl-bl?',
44689
44690     /**
44691      * @cfg {String} textAlign
44692      * The text alignment for this button (center, left, right).
44693      */
44694     textAlign: 'center',
44695
44696     /**
44697      * @cfg {String} overflowText
44698      * If used in a {@link Ext.toolbar.Toolbar Toolbar}, the text to be used if this item is shown in the overflow menu.
44699      * See also {@link Ext.toolbar.Item}.`{@link Ext.toolbar.Item#overflowText overflowText}`.
44700      */
44701
44702     /**
44703      * @cfg {String} iconCls
44704      * A css class which sets a background image to be used as the icon for this button.
44705      */
44706
44707     /**
44708      * @cfg {String} type
44709      * The type of `<input>` to create: submit, reset or button.
44710      */
44711     type: 'button',
44712
44713     /**
44714      * @cfg {String} clickEvent
44715      * The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu).
44716      */
44717     clickEvent: 'click',
44718
44719     /**
44720      * @cfg {Boolean} preventDefault
44721      * True to prevent the default action when the {@link #clickEvent} is processed.
44722      */
44723     preventDefault: true,
44724
44725     /**
44726      * @cfg {Boolean} handleMouseEvents
44727      * False to disable visual cues on mouseover, mouseout and mousedown.
44728      */
44729     handleMouseEvents: true,
44730
44731     /**
44732      * @cfg {String} tooltipType
44733      * The type of tooltip to use. Either 'qtip' for QuickTips or 'title' for title attribute.
44734      */
44735     tooltipType: 'qtip',
44736
44737     /**
44738      * @cfg {String} [baseCls='x-btn']
44739      * The base CSS class to add to all buttons.
44740      */
44741     baseCls: Ext.baseCSSPrefix + 'btn',
44742
44743     /**
44744      * @cfg {String} pressedCls
44745      * The CSS class to add to a button when it is in the pressed state.
44746      */
44747     pressedCls: 'pressed',
44748
44749     /**
44750      * @cfg {String} overCls
44751      * The CSS class to add to a button when it is in the over (hovered) state.
44752      */
44753     overCls: 'over',
44754
44755     /**
44756      * @cfg {String} focusCls
44757      * The CSS class to add to a button when it is in the focussed state.
44758      */
44759     focusCls: 'focus',
44760
44761     /**
44762      * @cfg {String} menuActiveCls
44763      * The CSS class to add to a button when it's menu is active.
44764      */
44765     menuActiveCls: 'menu-active',
44766
44767     /**
44768      * @cfg {String} href
44769      * The URL to visit when the button is clicked. Specifying this config is equivalent to specifying:
44770      *
44771      *     handler: function() { window.location = "http://www.sencha.com" }
44772      */
44773
44774     /**
44775      * @cfg {Object} baseParams
44776      * An object literal of parameters to pass to the url when the {@link #href} property is specified.
44777      */
44778
44779     /**
44780      * @cfg {Object} params
44781      * An object literal of parameters to pass to the url when the {@link #href} property is specified. Any params
44782      * override {@link #baseParams}. New params can be set using the {@link #setParams} method.
44783      */
44784
44785     ariaRole: 'button',
44786
44787     // inherited
44788     renderTpl:
44789         '<em id="{id}-btnWrap" class="{splitCls}">' +
44790             '<tpl if="href">' +
44791                 '<a id="{id}-btnEl" href="{href}" target="{target}"<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="link">' +
44792                     '<span id="{id}-btnInnerEl" class="{baseCls}-inner">' +
44793                         '{text}' +
44794                     '</span>' +
44795                         '<span id="{id}-btnIconEl" class="{baseCls}-icon"></span>' +
44796                 '</a>' +
44797             '</tpl>' +
44798             '<tpl if="!href">' +
44799                 '<button id="{id}-btnEl" type="{type}" hidefocus="true"' +
44800                     // the autocomplete="off" is required to prevent Firefox from remembering
44801                     // the button's disabled state between page reloads.
44802                     '<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="button" autocomplete="off">' +
44803                     '<span id="{id}-btnInnerEl" class="{baseCls}-inner" style="{innerSpanStyle}">' +
44804                         '{text}' +
44805                     '</span>' +
44806                     '<span id="{id}-btnIconEl" class="{baseCls}-icon {iconCls}">&#160;</span>' +
44807                 '</button>' +
44808             '</tpl>' +
44809         '</em>' ,
44810
44811     /**
44812      * @cfg {String} scale
44813      * The size of the Button. Three values are allowed:
44814      *
44815      * - 'small' - Results in the button element being 16px high.
44816      * - 'medium' - Results in the button element being 24px high.
44817      * - 'large' - Results in the button element being 32px high.
44818      */
44819     scale: 'small',
44820
44821     /**
44822      * @private
44823      * An array of allowed scales.
44824      */
44825     allowedScales: ['small', 'medium', 'large'],
44826
44827     /**
44828      * @cfg {Object} scope
44829      * The scope (**this** reference) in which the `{@link #handler}` and `{@link #toggleHandler}` is executed.
44830      * Defaults to this Button.
44831      */
44832
44833     /**
44834      * @cfg {String} iconAlign
44835      * The side of the Button box to render the icon. Four values are allowed:
44836      *
44837      * - 'top'
44838      * - 'right'
44839      * - 'bottom'
44840      * - 'left'
44841      */
44842     iconAlign: 'left',
44843
44844     /**
44845      * @cfg {String} arrowAlign
44846      * The side of the Button box to render the arrow if the button has an associated {@link #menu}. Two
44847      * values are allowed:
44848      *
44849      * - 'right'
44850      * - 'bottom'
44851      */
44852     arrowAlign: 'right',
44853
44854     /**
44855      * @cfg {String} arrowCls
44856      * The className used for the inner arrow element if the button has a menu.
44857      */
44858     arrowCls: 'arrow',
44859
44860     /**
44861      * @property {Ext.Template} template
44862      * A {@link Ext.Template Template} used to create the Button's DOM structure.
44863      *
44864      * Instances, or subclasses which need a different DOM structure may provide a different template layout in
44865      * conjunction with an implementation of {@link #getTemplateArgs}.
44866      */
44867
44868     /**
44869      * @cfg {String} cls
44870      * A CSS class string to apply to the button's main element.
44871      */
44872
44873     /**
44874      * @property {Ext.menu.Menu} menu
44875      * The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #menu} config
44876      * option.
44877      */
44878
44879     /**
44880      * @cfg {Boolean} autoWidth
44881      * By default, if a width is not specified the button will attempt to stretch horizontally to fit its content. If
44882      * the button is being managed by a width sizing layout (hbox, fit, anchor), set this to false to prevent the button
44883      * from doing this automatic sizing.
44884      */
44885
44886     maskOnDisable: false,
44887
44888     // inherit docs
44889     initComponent: function() {
44890         var me = this;
44891         me.callParent(arguments);
44892
44893         me.addEvents(
44894             /**
44895              * @event click
44896              * Fires when this button is clicked
44897              * @param {Ext.button.Button} this
44898              * @param {Event} e The click event
44899              */
44900             'click',
44901
44902             /**
44903              * @event toggle
44904              * Fires when the 'pressed' state of this button changes (only if enableToggle = true)
44905              * @param {Ext.button.Button} this
44906              * @param {Boolean} pressed
44907              */
44908             'toggle',
44909
44910             /**
44911              * @event mouseover
44912              * Fires when the mouse hovers over the button
44913              * @param {Ext.button.Button} this
44914              * @param {Event} e The event object
44915              */
44916             'mouseover',
44917
44918             /**
44919              * @event mouseout
44920              * Fires when the mouse exits the button
44921              * @param {Ext.button.Button} this
44922              * @param {Event} e The event object
44923              */
44924             'mouseout',
44925
44926             /**
44927              * @event menushow
44928              * If this button has a menu, this event fires when it is shown
44929              * @param {Ext.button.Button} this
44930              * @param {Ext.menu.Menu} menu
44931              */
44932             'menushow',
44933
44934             /**
44935              * @event menuhide
44936              * If this button has a menu, this event fires when it is hidden
44937              * @param {Ext.button.Button} this
44938              * @param {Ext.menu.Menu} menu
44939              */
44940             'menuhide',
44941
44942             /**
44943              * @event menutriggerover
44944              * If this button has a menu, this event fires when the mouse enters the menu triggering element
44945              * @param {Ext.button.Button} this
44946              * @param {Ext.menu.Menu} menu
44947              * @param {Event} e
44948              */
44949             'menutriggerover',
44950
44951             /**
44952              * @event menutriggerout
44953              * If this button has a menu, this event fires when the mouse leaves the menu triggering element
44954              * @param {Ext.button.Button} this
44955              * @param {Ext.menu.Menu} menu
44956              * @param {Event} e
44957              */
44958             'menutriggerout'
44959         );
44960
44961         if (me.menu) {
44962             // Flag that we'll have a splitCls
44963             me.split = true;
44964
44965             // retrieve menu by id or instantiate instance if needed
44966             me.menu = Ext.menu.Manager.get(me.menu);
44967             me.menu.ownerCt = me;
44968         }
44969
44970         // Accept url as a synonym for href
44971         if (me.url) {
44972             me.href = me.url;
44973         }
44974
44975         // preventDefault defaults to false for links
44976         if (me.href && !me.hasOwnProperty('preventDefault')) {
44977             me.preventDefault = false;
44978         }
44979
44980         if (Ext.isString(me.toggleGroup)) {
44981             me.enableToggle = true;
44982         }
44983
44984     },
44985
44986     // private
44987     initAria: function() {
44988         this.callParent();
44989         var actionEl = this.getActionEl();
44990         if (this.menu) {
44991             actionEl.dom.setAttribute('aria-haspopup', true);
44992         }
44993     },
44994
44995     // inherit docs
44996     getActionEl: function() {
44997         return this.btnEl;
44998     },
44999
45000     // inherit docs
45001     getFocusEl: function() {
45002         return this.btnEl;
45003     },
45004
45005     // private
45006     setButtonCls: function() {
45007         var me = this,
45008             cls = [],
45009             btnIconEl = me.btnIconEl,
45010             hide = 'x-hide-display';
45011
45012         if (me.useSetClass) {
45013             if (!Ext.isEmpty(me.oldCls)) {
45014                 me.removeClsWithUI(me.oldCls);
45015                 me.removeClsWithUI(me.pressedCls);
45016             }
45017
45018             // Check whether the button has an icon or not, and if it has an icon, what is th alignment
45019             if (me.iconCls || me.icon) {
45020                 if (me.text) {
45021                     cls.push('icon-text-' + me.iconAlign);
45022                 } else {
45023                     cls.push('icon');
45024                 }
45025                 if (btnIconEl) {
45026                     btnIconEl.removeCls(hide);
45027                 }
45028             } else {
45029                 if (me.text) {
45030                     cls.push('noicon');
45031                 }
45032                 if (btnIconEl) {
45033                     btnIconEl.addCls(hide);
45034                 }
45035             }
45036
45037             me.oldCls = cls;
45038             me.addClsWithUI(cls);
45039             me.addClsWithUI(me.pressed ? me.pressedCls : null);
45040         }
45041     },
45042
45043     // private
45044     onRender: function(ct, position) {
45045         // classNames for the button
45046         var me = this,
45047             repeater, btn;
45048
45049         // Apply the renderData to the template args
45050         Ext.applyIf(me.renderData, me.getTemplateArgs());
45051
45052         me.addChildEls('btnEl', 'btnWrap', 'btnInnerEl', 'btnIconEl');
45053
45054         if (me.scale) {
45055             me.ui = me.ui + '-' + me.scale;
45056         }
45057
45058         // Render internal structure
45059         me.callParent(arguments);
45060
45061         // If it is a split button + has a toolip for the arrow
45062         if (me.split && me.arrowTooltip) {
45063             me.arrowEl.dom.setAttribute(me.getTipAttr(), me.arrowTooltip);
45064         }
45065
45066         // Add listeners to the focus and blur events on the element
45067         me.mon(me.btnEl, {
45068             scope: me,
45069             focus: me.onFocus,
45070             blur : me.onBlur
45071         });
45072
45073         // Set btn as a local variable for easy access
45074         btn = me.el;
45075
45076         if (me.icon) {
45077             me.setIcon(me.icon);
45078         }
45079
45080         if (me.iconCls) {
45081             me.setIconCls(me.iconCls);
45082         }
45083
45084         if (me.tooltip) {
45085             me.setTooltip(me.tooltip, true);
45086         }
45087
45088         if (me.textAlign) {
45089             me.setTextAlign(me.textAlign);
45090         }
45091
45092         // Add the mouse events to the button
45093         if (me.handleMouseEvents) {
45094             me.mon(btn, {
45095                 scope: me,
45096                 mouseover: me.onMouseOver,
45097                 mouseout: me.onMouseOut,
45098                 mousedown: me.onMouseDown
45099             });
45100
45101             if (me.split) {
45102                 me.mon(btn, {
45103                     mousemove: me.onMouseMove,
45104                     scope: me
45105                 });
45106             }
45107         }
45108
45109         // Check if the button has a menu
45110         if (me.menu) {
45111             me.mon(me.menu, {
45112                 scope: me,
45113                 show: me.onMenuShow,
45114                 hide: me.onMenuHide
45115             });
45116
45117             me.keyMap = Ext.create('Ext.util.KeyMap', me.el, {
45118                 key: Ext.EventObject.DOWN,
45119                 handler: me.onDownKey,
45120                 scope: me
45121             });
45122         }
45123
45124         // Check if it is a repeat button
45125         if (me.repeat) {
45126             repeater = Ext.create('Ext.util.ClickRepeater', btn, Ext.isObject(me.repeat) ? me.repeat: {});
45127             me.mon(repeater, 'click', me.onRepeatClick, me);
45128         } else {
45129             me.mon(btn, me.clickEvent, me.onClick, me);
45130         }
45131
45132         // Register the button in the toggle manager
45133         Ext.ButtonToggleManager.register(me);
45134     },
45135
45136     /**
45137      * This method returns an object which provides substitution parameters for the {@link #renderTpl XTemplate} used to
45138      * create this Button's DOM structure.
45139      *
45140      * Instances or subclasses which use a different Template to create a different DOM structure may need to provide
45141      * their own implementation of this method.
45142      *
45143      * @return {Object} Substitution data for a Template. The default implementation which provides data for the default
45144      * {@link #template} returns an Object containing the following properties:
45145      * @return {String} return.type The `<button>`'s {@link #type}
45146      * @return {String} return.splitCls A CSS class to determine the presence and position of an arrow icon.
45147      * (`'x-btn-arrow'` or `'x-btn-arrow-bottom'` or `''`)
45148      * @return {String} return.cls A CSS class name applied to the Button's main `<tbody>` element which determines the
45149      * button's scale and icon alignment.
45150      * @return {String} return.text The {@link #text} to display ion the Button.
45151      * @return {Number} return.tabIndex The tab index within the input flow.
45152      */
45153     getTemplateArgs: function() {
45154         var me = this,
45155             persistentPadding = me.getPersistentBtnPadding(),
45156             innerSpanStyle = '';
45157
45158         // Create negative margin offsets to counteract persistent button padding if needed
45159         if (Math.max.apply(Math, persistentPadding) > 0) {
45160             innerSpanStyle = 'margin:' + Ext.Array.map(persistentPadding, function(pad) {
45161                 return -pad + 'px';
45162             }).join(' ');
45163         }
45164
45165         return {
45166             href     : me.getHref(),
45167             target   : me.target || '_blank',
45168             type     : me.type,
45169             splitCls : me.getSplitCls(),
45170             cls      : me.cls,
45171             iconCls  : me.iconCls || '',
45172             text     : me.text || '&#160;',
45173             tabIndex : me.tabIndex,
45174             innerSpanStyle: innerSpanStyle
45175         };
45176     },
45177
45178     /**
45179      * @private
45180      * If there is a configured href for this Button, returns the href with parameters appended.
45181      * @returns The href string with parameters appended.
45182      */
45183     getHref: function() {
45184         var me = this,
45185             params = Ext.apply({}, me.baseParams);
45186
45187         // write baseParams first, then write any params
45188         params = Ext.apply(params, me.params);
45189         return me.href ? Ext.urlAppend(me.href, Ext.Object.toQueryString(params)) : false;
45190     },
45191
45192     /**
45193      * Sets the href of the link dynamically according to the params passed, and any {@link #baseParams} configured.
45194      *
45195      * **Only valid if the Button was originally configured with a {@link #href}**
45196      *
45197      * @param {Object} params Parameters to use in the href URL.
45198      */
45199     setParams: function(params) {
45200         this.params = params;
45201         this.btnEl.dom.href = this.getHref();
45202     },
45203
45204     getSplitCls: function() {
45205         var me = this;
45206         return me.split ? (me.baseCls + '-' + me.arrowCls) + ' ' + (me.baseCls + '-' + me.arrowCls + '-' + me.arrowAlign) : '';
45207     },
45208
45209     // private
45210     afterRender: function() {
45211         var me = this;
45212         me.useSetClass = true;
45213         me.setButtonCls();
45214         me.doc = Ext.getDoc();
45215         this.callParent(arguments);
45216     },
45217
45218     /**
45219      * Sets the CSS class that provides a background image to use as the button's icon. This method also changes the
45220      * value of the {@link #iconCls} config internally.
45221      * @param {String} cls The CSS class providing the icon image
45222      * @return {Ext.button.Button} this
45223      */
45224     setIconCls: function(cls) {
45225         var me = this,
45226             btnIconEl = me.btnIconEl,
45227             oldCls = me.iconCls;
45228             
45229         me.iconCls = cls;
45230         if (btnIconEl) {
45231             // Remove the previous iconCls from the button
45232             btnIconEl.removeCls(oldCls);
45233             btnIconEl.addCls(cls || '');
45234             me.setButtonCls();
45235         }
45236         return me;
45237     },
45238
45239     /**
45240      * Sets the tooltip for this Button.
45241      *
45242      * @param {String/Object} tooltip This may be:
45243      *
45244      *   - **String** : A string to be used as innerHTML (html tags are accepted) to show in a tooltip
45245      *   - **Object** : A configuration object for {@link Ext.tip.QuickTipManager#register}.
45246      *
45247      * @return {Ext.button.Button} this
45248      */
45249     setTooltip: function(tooltip, initial) {
45250         var me = this;
45251
45252         if (me.rendered) {
45253             if (!initial) {
45254                 me.clearTip();
45255             }
45256             if (Ext.isObject(tooltip)) {
45257                 Ext.tip.QuickTipManager.register(Ext.apply({
45258                     target: me.btnEl.id
45259                 },
45260                 tooltip));
45261                 me.tooltip = tooltip;
45262             } else {
45263                 me.btnEl.dom.setAttribute(me.getTipAttr(), tooltip);
45264             }
45265         } else {
45266             me.tooltip = tooltip;
45267         }
45268         return me;
45269     },
45270
45271     /**
45272      * Sets the text alignment for this button.
45273      * @param {String} align The new alignment of the button text. See {@link #textAlign}.
45274      */
45275     setTextAlign: function(align) {
45276         var me = this,
45277             btnEl = me.btnEl;
45278
45279         if (btnEl) {
45280             btnEl.removeCls(me.baseCls + '-' + me.textAlign);
45281             btnEl.addCls(me.baseCls + '-' + align);
45282         }
45283         me.textAlign = align;
45284         return me;
45285     },
45286
45287     getTipAttr: function(){
45288         return this.tooltipType == 'qtip' ? 'data-qtip' : 'title';
45289     },
45290
45291     // private
45292     getRefItems: function(deep){
45293         var menu = this.menu,
45294             items;
45295         
45296         if (menu) {
45297             items = menu.getRefItems(deep);
45298             items.unshift(menu);
45299         }
45300         return items || [];
45301     },
45302
45303     // private
45304     clearTip: function() {
45305         if (Ext.isObject(this.tooltip)) {
45306             Ext.tip.QuickTipManager.unregister(this.btnEl);
45307         }
45308     },
45309
45310     // private
45311     beforeDestroy: function() {
45312         var me = this;
45313         if (me.rendered) {
45314             me.clearTip();
45315         }
45316         if (me.menu && me.destroyMenu !== false) {
45317             Ext.destroy(me.menu);
45318         }
45319         Ext.destroy(me.btnInnerEl, me.repeater);
45320         me.callParent();
45321     },
45322
45323     // private
45324     onDestroy: function() {
45325         var me = this;
45326         if (me.rendered) {
45327             me.doc.un('mouseover', me.monitorMouseOver, me);
45328             me.doc.un('mouseup', me.onMouseUp, me);
45329             delete me.doc;
45330             Ext.ButtonToggleManager.unregister(me);
45331
45332             Ext.destroy(me.keyMap);
45333             delete me.keyMap;
45334         }
45335         me.callParent();
45336     },
45337
45338     /**
45339      * Assigns this Button's click handler
45340      * @param {Function} handler The function to call when the button is clicked
45341      * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed.
45342      * Defaults to this Button.
45343      * @return {Ext.button.Button} this
45344      */
45345     setHandler: function(handler, scope) {
45346         this.handler = handler;
45347         this.scope = scope;
45348         return this;
45349     },
45350
45351     /**
45352      * Sets this Button's text
45353      * @param {String} text The button text
45354      * @return {Ext.button.Button} this
45355      */
45356     setText: function(text) {
45357         var me = this;
45358         me.text = text;
45359         if (me.el) {
45360             me.btnInnerEl.update(text || '&#160;');
45361             me.setButtonCls();
45362         }
45363         me.doComponentLayout();
45364         return me;
45365     },
45366
45367     /**
45368      * Sets the background image (inline style) of the button. This method also changes the value of the {@link #icon}
45369      * config internally.
45370      * @param {String} icon The path to an image to display in the button
45371      * @return {Ext.button.Button} this
45372      */
45373     setIcon: function(icon) {
45374         var me = this,
45375             iconEl = me.btnIconEl;
45376             
45377         me.icon = icon;
45378         if (iconEl) {
45379             iconEl.setStyle('background-image', icon ? 'url(' + icon + ')': '');
45380             me.setButtonCls();
45381         }
45382         return me;
45383     },
45384
45385     /**
45386      * Gets the text for this Button
45387      * @return {String} The button text
45388      */
45389     getText: function() {
45390         return this.text;
45391     },
45392
45393     /**
45394      * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
45395      * @param {Boolean} [state] Force a particular state
45396      * @param {Boolean} [suppressEvent=false] True to stop events being fired when calling this method.
45397      * @return {Ext.button.Button} this
45398      */
45399     toggle: function(state, suppressEvent) {
45400         var me = this;
45401         state = state === undefined ? !me.pressed : !!state;
45402         if (state !== me.pressed) {
45403             if (me.rendered) {
45404                 me[state ? 'addClsWithUI': 'removeClsWithUI'](me.pressedCls);
45405             }
45406             me.btnEl.dom.setAttribute('aria-pressed', state);
45407             me.pressed = state;
45408             if (!suppressEvent) {
45409                 me.fireEvent('toggle', me, state);
45410                 Ext.callback(me.toggleHandler, me.scope || me, [me, state]);
45411             }
45412         }
45413         return me;
45414     },
45415     
45416     maybeShowMenu: function(){
45417         var me = this;
45418         if (me.menu && !me.hasVisibleMenu() && !me.ignoreNextClick) {
45419             me.showMenu();
45420         }
45421     },
45422
45423     /**
45424      * Shows this button's menu (if it has one)
45425      */
45426     showMenu: function() {
45427         var me = this;
45428         if (me.rendered && me.menu) {
45429             if (me.tooltip && me.getTipAttr() != 'title') {
45430                 Ext.tip.QuickTipManager.getQuickTip().cancelShow(me.btnEl);
45431             }
45432             if (me.menu.isVisible()) {
45433                 me.menu.hide();
45434             }
45435
45436             me.menu.showBy(me.el, me.menuAlign);
45437         }
45438         return me;
45439     },
45440
45441     /**
45442      * Hides this button's menu (if it has one)
45443      */
45444     hideMenu: function() {
45445         if (this.hasVisibleMenu()) {
45446             this.menu.hide();
45447         }
45448         return this;
45449     },
45450
45451     /**
45452      * Returns true if the button has a menu and it is visible
45453      * @return {Boolean}
45454      */
45455     hasVisibleMenu: function() {
45456         var menu = this.menu;
45457         return menu && menu.rendered && menu.isVisible();
45458     },
45459
45460     // private
45461     onRepeatClick: function(repeat, e) {
45462         this.onClick(e);
45463     },
45464
45465     // private
45466     onClick: function(e) {
45467         var me = this;
45468         if (me.preventDefault || (me.disabled && me.getHref()) && e) {
45469             e.preventDefault();
45470         }
45471         if (e.button !== 0) {
45472             return;
45473         }
45474         if (!me.disabled) {
45475             me.doToggle();
45476             me.maybeShowMenu();
45477             me.fireHandler(e);
45478         }
45479     },
45480     
45481     fireHandler: function(e){
45482         var me = this,
45483             handler = me.handler;
45484             
45485         me.fireEvent('click', me, e);
45486         if (handler) {
45487             handler.call(me.scope || me, me, e);
45488         }
45489         me.onBlur();
45490     },
45491     
45492     doToggle: function(){
45493         var me = this;
45494         if (me.enableToggle && (me.allowDepress !== false || !me.pressed)) {
45495             me.toggle();
45496         }
45497     },
45498
45499     /**
45500      * @private mouseover handler called when a mouseover event occurs anywhere within the encapsulating element.
45501      * The targets are interrogated to see what is being entered from where.
45502      * @param e
45503      */
45504     onMouseOver: function(e) {
45505         var me = this;
45506         if (!me.disabled && !e.within(me.el, true, true)) {
45507             me.onMouseEnter(e);
45508         }
45509     },
45510
45511     /**
45512      * @private
45513      * mouseout handler called when a mouseout event occurs anywhere within the encapsulating element -
45514      * or the mouse leaves the encapsulating element.
45515      * The targets are interrogated to see what is being exited to where.
45516      * @param e
45517      */
45518     onMouseOut: function(e) {
45519         var me = this;
45520         if (!e.within(me.el, true, true)) {
45521             if (me.overMenuTrigger) {
45522                 me.onMenuTriggerOut(e);
45523             }
45524             me.onMouseLeave(e);
45525         }
45526     },
45527
45528     /**
45529      * @private
45530      * mousemove handler called when the mouse moves anywhere within the encapsulating element.
45531      * The position is checked to determine if the mouse is entering or leaving the trigger area. Using
45532      * mousemove to check this is more resource intensive than we'd like, but it is necessary because
45533      * the trigger area does not line up exactly with sub-elements so we don't always get mouseover/out
45534      * events when needed. In the future we should consider making the trigger a separate element that
45535      * is absolutely positioned and sized over the trigger area.
45536      */
45537     onMouseMove: function(e) {
45538         var me = this,
45539             el = me.el,
45540             over = me.overMenuTrigger,
45541             overlap, btnSize;
45542
45543         if (me.split) {
45544             if (me.arrowAlign === 'right') {
45545                 overlap = e.getX() - el.getX();
45546                 btnSize = el.getWidth();
45547             } else {
45548                 overlap = e.getY() - el.getY();
45549                 btnSize = el.getHeight();
45550             }
45551
45552             if (overlap > (btnSize - me.getTriggerSize())) {
45553                 if (!over) {
45554                     me.onMenuTriggerOver(e);
45555                 }
45556             } else {
45557                 if (over) {
45558                     me.onMenuTriggerOut(e);
45559                 }
45560             }
45561         }
45562     },
45563
45564     /**
45565      * @private
45566      * Measures the size of the trigger area for menu and split buttons. Will be a width for
45567      * a right-aligned trigger and a height for a bottom-aligned trigger. Cached after first measurement.
45568      */
45569     getTriggerSize: function() {
45570         var me = this,
45571             size = me.triggerSize,
45572             side, sideFirstLetter, undef;
45573
45574         if (size === undef) {
45575             side = me.arrowAlign;
45576             sideFirstLetter = side.charAt(0);
45577             size = me.triggerSize = me.el.getFrameWidth(sideFirstLetter) + me.btnWrap.getFrameWidth(sideFirstLetter) + (me.frameSize && me.frameSize[side] || 0);
45578         }
45579         return size;
45580     },
45581
45582     /**
45583      * @private
45584      * virtual mouseenter handler called when it is detected that the mouseout event
45585      * signified the mouse entering the encapsulating element.
45586      * @param e
45587      */
45588     onMouseEnter: function(e) {
45589         var me = this;
45590         me.addClsWithUI(me.overCls);
45591         me.fireEvent('mouseover', me, e);
45592     },
45593
45594     /**
45595      * @private
45596      * virtual mouseleave handler called when it is detected that the mouseover event
45597      * signified the mouse entering the encapsulating element.
45598      * @param e
45599      */
45600     onMouseLeave: function(e) {
45601         var me = this;
45602         me.removeClsWithUI(me.overCls);
45603         me.fireEvent('mouseout', me, e);
45604     },
45605
45606     /**
45607      * @private
45608      * virtual mouseenter handler called when it is detected that the mouseover event
45609      * signified the mouse entering the arrow area of the button - the <em>.
45610      * @param e
45611      */
45612     onMenuTriggerOver: function(e) {
45613         var me = this;
45614         me.overMenuTrigger = true;
45615         me.fireEvent('menutriggerover', me, me.menu, e);
45616     },
45617
45618     /**
45619      * @private
45620      * virtual mouseleave handler called when it is detected that the mouseout event
45621      * signified the mouse leaving the arrow area of the button - the <em>.
45622      * @param e
45623      */
45624     onMenuTriggerOut: function(e) {
45625         var me = this;
45626         delete me.overMenuTrigger;
45627         me.fireEvent('menutriggerout', me, me.menu, e);
45628     },
45629
45630     // inherit docs
45631     enable : function(silent) {
45632         var me = this;
45633
45634         me.callParent(arguments);
45635
45636         me.removeClsWithUI('disabled');
45637
45638         return me;
45639     },
45640
45641     // inherit docs
45642     disable : function(silent) {
45643         var me = this;
45644
45645         me.callParent(arguments);
45646
45647         me.addClsWithUI('disabled');
45648         me.removeClsWithUI(me.overCls);
45649
45650         return me;
45651     },
45652
45653     /**
45654      * Method to change the scale of the button. See {@link #scale} for allowed configurations.
45655      * @param {String} scale The scale to change to.
45656      */
45657     setScale: function(scale) {
45658         var me = this,
45659             ui = me.ui.replace('-' + me.scale, '');
45660
45661         //check if it is an allowed scale
45662         if (!Ext.Array.contains(me.allowedScales, scale)) {
45663             throw('#setScale: scale must be an allowed scale (' + me.allowedScales.join(', ') + ')');
45664         }
45665
45666         me.scale = scale;
45667         me.setUI(ui);
45668     },
45669
45670     // inherit docs
45671     setUI: function(ui) {
45672         var me = this;
45673
45674         //we need to append the scale to the UI, if not already done
45675         if (me.scale && !ui.match(me.scale)) {
45676             ui = ui + '-' + me.scale;
45677         }
45678
45679         me.callParent([ui]);
45680
45681         // Set all the state classNames, as they need to include the UI
45682         // me.disabledCls += ' ' + me.baseCls + '-' + me.ui + '-disabled';
45683     },
45684
45685     // private
45686     onFocus: function(e) {
45687         var me = this;
45688         if (!me.disabled) {
45689             me.addClsWithUI(me.focusCls);
45690         }
45691     },
45692
45693     // private
45694     onBlur: function(e) {
45695         var me = this;
45696         me.removeClsWithUI(me.focusCls);
45697     },
45698
45699     // private
45700     onMouseDown: function(e) {
45701         var me = this;
45702         if (!me.disabled && e.button === 0) {
45703             me.addClsWithUI(me.pressedCls);
45704             me.doc.on('mouseup', me.onMouseUp, me);
45705         }
45706     },
45707     // private
45708     onMouseUp: function(e) {
45709         var me = this;
45710         if (e.button === 0) {
45711             if (!me.pressed) {
45712                 me.removeClsWithUI(me.pressedCls);
45713             }
45714             me.doc.un('mouseup', me.onMouseUp, me);
45715         }
45716     },
45717     // private
45718     onMenuShow: function(e) {
45719         var me = this;
45720         me.ignoreNextClick = 0;
45721         me.addClsWithUI(me.menuActiveCls);
45722         me.fireEvent('menushow', me, me.menu);
45723     },
45724
45725     // private
45726     onMenuHide: function(e) {
45727         var me = this;
45728         me.removeClsWithUI(me.menuActiveCls);
45729         me.ignoreNextClick = Ext.defer(me.restoreClick, 250, me);
45730         me.fireEvent('menuhide', me, me.menu);
45731     },
45732
45733     // private
45734     restoreClick: function() {
45735         this.ignoreNextClick = 0;
45736     },
45737
45738     // private
45739     onDownKey: function() {
45740         var me = this;
45741
45742         if (!me.disabled) {
45743             if (me.menu) {
45744                 me.showMenu();
45745             }
45746         }
45747     },
45748
45749     /**
45750      * @private
45751      * Some browsers (notably Safari and older Chromes on Windows) add extra "padding" inside the button
45752      * element that cannot be removed. This method returns the size of that padding with a one-time detection.
45753      * @return {Number[]} [top, right, bottom, left]
45754      */
45755     getPersistentBtnPadding: function() {
45756         var cls = Ext.button.Button,
45757             padding = cls.persistentPadding,
45758             btn, leftTop, btnEl, btnInnerEl;
45759
45760         if (!padding) {
45761             padding = cls.persistentPadding = [0, 0, 0, 0]; //set early to prevent recursion
45762
45763             if (!Ext.isIE) { //short-circuit IE as it sometimes gives false positive for padding
45764                 // Create auto-size button offscreen and measure its insides
45765                 btn = Ext.create('Ext.button.Button', {
45766                     renderTo: Ext.getBody(),
45767                     text: 'test',
45768                     style: 'position:absolute;top:-999px;'
45769                 });
45770                 btnEl = btn.btnEl;
45771                 btnInnerEl = btn.btnInnerEl;
45772                 btnEl.setSize(null, null); //clear any hard dimensions on the button el to see what it does naturally
45773
45774                 leftTop = btnInnerEl.getOffsetsTo(btnEl);
45775                 padding[0] = leftTop[1];
45776                 padding[1] = btnEl.getWidth() - btnInnerEl.getWidth() - leftTop[0];
45777                 padding[2] = btnEl.getHeight() - btnInnerEl.getHeight() - leftTop[1];
45778                 padding[3] = leftTop[0];
45779
45780                 btn.destroy();
45781             }
45782         }
45783
45784         return padding;
45785     }
45786
45787 }, function() {
45788     var groups = {};
45789
45790     function toggleGroup(btn, state) {
45791         var g, i, l;
45792         if (state) {
45793             g = groups[btn.toggleGroup];
45794             for (i = 0, l = g.length; i < l; i++) {
45795                 if (g[i] !== btn) {
45796                     g[i].toggle(false);
45797                 }
45798             }
45799         }
45800     }
45801
45802     /**
45803      * Private utility class used by Button
45804      * @hide
45805      */
45806     Ext.ButtonToggleManager = {
45807         register: function(btn) {
45808             if (!btn.toggleGroup) {
45809                 return;
45810             }
45811             var group = groups[btn.toggleGroup];
45812             if (!group) {
45813                 group = groups[btn.toggleGroup] = [];
45814             }
45815             group.push(btn);
45816             btn.on('toggle', toggleGroup);
45817         },
45818
45819         unregister: function(btn) {
45820             if (!btn.toggleGroup) {
45821                 return;
45822             }
45823             var group = groups[btn.toggleGroup];
45824             if (group) {
45825                 Ext.Array.remove(group, btn);
45826                 btn.un('toggle', toggleGroup);
45827             }
45828         },
45829
45830         /**
45831          * Gets the pressed button in the passed group or null
45832          * @param {String} group
45833          * @return {Ext.button.Button}
45834          */
45835         getPressed: function(group) {
45836             var g = groups[group],
45837                 i = 0,
45838                 len;
45839             if (g) {
45840                 for (len = g.length; i < len; i++) {
45841                     if (g[i].pressed === true) {
45842                         return g[i];
45843                     }
45844                 }
45845             }
45846             return null;
45847         }
45848     };
45849 });
45850
45851 /**
45852  * @class Ext.layout.container.boxOverflow.Menu
45853  * @extends Ext.layout.container.boxOverflow.None
45854  * @private
45855  */
45856 Ext.define('Ext.layout.container.boxOverflow.Menu', {
45857
45858     /* Begin Definitions */
45859
45860     extend: 'Ext.layout.container.boxOverflow.None',
45861     requires: ['Ext.toolbar.Separator', 'Ext.button.Button'],
45862     alternateClassName: 'Ext.layout.boxOverflow.Menu',
45863     
45864     /* End Definitions */
45865
45866     /**
45867      * @cfg {String} afterCtCls
45868      * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
45869      * which must always be present at the rightmost edge of the Container
45870      */
45871
45872     /**
45873      * @property noItemsMenuText
45874      * @type String
45875      * HTML fragment to render into the toolbar overflow menu if there are no items to display
45876      */
45877     noItemsMenuText : '<div class="' + Ext.baseCSSPrefix + 'toolbar-no-items">(None)</div>',
45878
45879     constructor: function(layout) {
45880         var me = this;
45881
45882         me.callParent(arguments);
45883
45884         // Before layout, we need to re-show all items which we may have hidden due to a previous overflow.
45885         layout.beforeLayout = Ext.Function.createInterceptor(layout.beforeLayout, this.clearOverflow, this);
45886
45887         me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-menu-' + layout.parallelAfter;
45888         /**
45889          * @property menuItems
45890          * @type Array
45891          * Array of all items that are currently hidden and should go into the dropdown menu
45892          */
45893         me.menuItems = [];
45894     },
45895     
45896     onRemove: function(comp){
45897         Ext.Array.remove(this.menuItems, comp);
45898     },
45899
45900     handleOverflow: function(calculations, targetSize) {
45901         var me = this,
45902             layout = me.layout,
45903             methodName = 'get' + layout.parallelPrefixCap,
45904             newSize = {},
45905             posArgs = [null, null];
45906
45907         me.callParent(arguments);
45908         this.createMenu(calculations, targetSize);
45909         newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
45910         newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - me.afterCt[methodName]();
45911
45912         // Center the menuTrigger button.
45913         // TODO: Should we emulate align: 'middle' like this, or should we 'stretchmax' the menuTrigger?
45914         posArgs[layout.perpendicularSizeIndex] = (calculations.meta.maxSize - me.menuTrigger['get' + layout.perpendicularPrefixCap]()) / 2;
45915         me.menuTrigger.setPosition.apply(me.menuTrigger, posArgs);
45916
45917         return { targetSize: newSize };
45918     },
45919
45920     /**
45921      * @private
45922      * Called by the layout, when it determines that there is no overflow.
45923      * Also called as an interceptor to the layout's onLayout method to reshow
45924      * previously hidden overflowing items.
45925      */
45926     clearOverflow: function(calculations, targetSize) {
45927         var me = this,
45928             newWidth = targetSize ? targetSize.width + (me.afterCt ? me.afterCt.getWidth() : 0) : 0,
45929             items = me.menuItems,
45930             i = 0,
45931             length = items.length,
45932             item;
45933
45934         me.hideTrigger();
45935         for (; i < length; i++) {
45936             items[i].show();
45937         }
45938         items.length = 0;
45939
45940         return targetSize ? {
45941             targetSize: {
45942                 height: targetSize.height,
45943                 width : newWidth
45944             }
45945         } : null;
45946     },
45947
45948     /**
45949      * @private
45950      */
45951     showTrigger: function() {
45952         this.menuTrigger.show();
45953     },
45954
45955     /**
45956      * @private
45957      */
45958     hideTrigger: function() {
45959         if (this.menuTrigger !== undefined) {
45960             this.menuTrigger.hide();
45961         }
45962     },
45963
45964     /**
45965      * @private
45966      * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can.
45967      */
45968     beforeMenuShow: function(menu) {
45969         var me = this,
45970             items = me.menuItems,
45971             i = 0,
45972             len   = items.length,
45973             item,
45974             prev;
45975
45976         var needsSep = function(group, prev){
45977             return group.isXType('buttongroup') && !(prev instanceof Ext.toolbar.Separator);
45978         };
45979
45980         me.clearMenu();
45981         menu.removeAll();
45982
45983         for (; i < len; i++) {
45984             item = items[i];
45985
45986             // Do not show a separator as a first item
45987             if (!i && (item instanceof Ext.toolbar.Separator)) {
45988                 continue;
45989             }
45990             if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
45991                 menu.add('-');
45992             }
45993
45994             me.addComponentToMenu(menu, item);
45995             prev = item;
45996         }
45997
45998         // put something so the menu isn't empty if no compatible items found
45999         if (menu.items.length < 1) {
46000             menu.add(me.noItemsMenuText);
46001         }
46002     },
46003     
46004     /**
46005      * @private
46006      * Returns a menu config for a given component. This config is used to create a menu item
46007      * to be added to the expander menu
46008      * @param {Ext.Component} component The component to create the config for
46009      * @param {Boolean} hideOnClick Passed through to the menu item
46010      */
46011     createMenuConfig : function(component, hideOnClick) {
46012         var config = Ext.apply({}, component.initialConfig),
46013             group  = component.toggleGroup;
46014
46015         Ext.copyTo(config, component, [
46016             'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
46017         ]);
46018
46019         Ext.apply(config, {
46020             text       : component.overflowText || component.text,
46021             hideOnClick: hideOnClick,
46022             destroyMenu: false
46023         });
46024
46025         if (group || component.enableToggle) {
46026             Ext.apply(config, {
46027                 group  : group,
46028                 checked: component.pressed,
46029                 listeners: {
46030                     checkchange: function(item, checked){
46031                         component.toggle(checked);
46032                     }
46033                 }
46034             });
46035         }
46036
46037         delete config.ownerCt;
46038         delete config.xtype;
46039         delete config.id;
46040         return config;
46041     },
46042
46043     /**
46044      * @private
46045      * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
46046      * @param {Ext.menu.Menu} menu The menu to add to
46047      * @param {Ext.Component} component The component to add
46048      */
46049     addComponentToMenu : function(menu, component) {
46050         var me = this;
46051         if (component instanceof Ext.toolbar.Separator) {
46052             menu.add('-');
46053         } else if (component.isComponent) {
46054             if (component.isXType('splitbutton')) {
46055                 menu.add(me.createMenuConfig(component, true));
46056
46057             } else if (component.isXType('button')) {
46058                 menu.add(me.createMenuConfig(component, !component.menu));
46059
46060             } else if (component.isXType('buttongroup')) {
46061                 component.items.each(function(item){
46062                      me.addComponentToMenu(menu, item);
46063                 });
46064             } else {
46065                 menu.add(Ext.create(Ext.getClassName(component), me.createMenuConfig(component)));
46066             }
46067         }
46068     },
46069
46070     /**
46071      * @private
46072      * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
46073      * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
46074      */
46075     clearMenu : function() {
46076         var menu = this.moreMenu;
46077         if (menu && menu.items) {
46078             menu.items.each(function(item) {
46079                 if (item.menu) {
46080                     delete item.menu;
46081                 }
46082             });
46083         }
46084     },
46085
46086     /**
46087      * @private
46088      * Creates the overflow trigger and menu used when enableOverflow is set to true and the items
46089      * in the layout are too wide to fit in the space available
46090      */
46091     createMenu: function(calculations, targetSize) {
46092         var me = this,
46093             layout = me.layout,
46094             startProp = layout.parallelBefore,
46095             sizeProp = layout.parallelPrefix,
46096             available = targetSize[sizeProp],
46097             boxes = calculations.boxes,
46098             i = 0,
46099             len = boxes.length,
46100             box;
46101
46102         if (!me.menuTrigger) {
46103             me.createInnerElements();
46104
46105             /**
46106              * @private
46107              * @property menu
46108              * @type Ext.menu.Menu
46109              * The expand menu - holds items for every item that cannot be shown
46110              * because the container is currently not large enough.
46111              */
46112             me.menu = Ext.create('Ext.menu.Menu', {
46113                 listeners: {
46114                     scope: me,
46115                     beforeshow: me.beforeMenuShow
46116                 }
46117             });
46118
46119             /**
46120              * @private
46121              * @property menuTrigger
46122              * @type Ext.button.Button
46123              * The expand button which triggers the overflow menu to be shown
46124              */
46125             me.menuTrigger = Ext.create('Ext.button.Button', {
46126                 ownerCt : me.layout.owner, // To enable the Menu to ascertain a valid zIndexManager owner in the same tree
46127                 iconCls : me.layout.owner.menuTriggerCls,
46128                 ui      : layout.owner instanceof Ext.toolbar.Toolbar ? 'default-toolbar' : 'default',
46129                 menu    : me.menu,
46130                 getSplitCls: function() { return '';},
46131                 renderTo: me.afterCt
46132             });
46133         }
46134         me.showTrigger();
46135         available -= me.afterCt.getWidth();
46136
46137         // Hide all items which are off the end, and store them to allow them to be restored
46138         // before each layout operation.
46139         me.menuItems.length = 0;
46140         for (; i < len; i++) {
46141             box = boxes[i];
46142             if (box[startProp] + box[sizeProp] > available) {
46143                 me.menuItems.push(box.component);
46144                 box.component.hide();
46145             }
46146         }
46147     },
46148
46149     /**
46150      * @private
46151      * Creates the beforeCt, innerCt and afterCt elements if they have not already been created
46152      * @param {Ext.container.Container} container The Container attached to this Layout instance
46153      * @param {Ext.Element} target The target Element
46154      */
46155     createInnerElements: function() {
46156         var me = this,
46157             target = me.layout.getRenderTarget();
46158
46159         if (!this.afterCt) {
46160             target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
46161             this.afterCt  = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + this.afterCtCls}, 'before');
46162         }
46163     },
46164
46165     /**
46166      * @private
46167      */
46168     destroy: function() {
46169         Ext.destroy(this.menu, this.menuTrigger);
46170     }
46171 });
46172 /**
46173  * This class represents a rectangular region in X,Y space, and performs geometric
46174  * transformations or tests upon the region.
46175  *
46176  * This class may be used to compare the document regions occupied by elements.
46177  */
46178 Ext.define('Ext.util.Region', {
46179
46180     /* Begin Definitions */
46181
46182     requires: ['Ext.util.Offset'],
46183
46184     statics: {
46185         /**
46186          * @static
46187          * Retrieves an Ext.util.Region for a particular element.
46188          * @param {String/HTMLElement/Ext.Element} el An element ID, htmlElement or Ext.Element representing an element in the document.
46189          * @returns {Ext.util.Region} region
46190          */
46191         getRegion: function(el) {
46192             return Ext.fly(el).getPageBox(true);
46193         },
46194
46195         /**
46196          * @static
46197          * Creates a Region from a "box" Object which contains four numeric properties `top`, `right`, `bottom` and `left`.
46198          * @param {Object} o An object with `top`, `right`, `bottom` and `left` properties.
46199          * @return {Ext.util.Region} region The Region constructed based on the passed object
46200          */
46201         from: function(o) {
46202             return new this(o.top, o.right, o.bottom, o.left);
46203         }
46204     },
46205
46206     /* End Definitions */
46207
46208     /**
46209      * Creates a region from the bounding sides.
46210      * @param {Number} top Top The topmost pixel of the Region.
46211      * @param {Number} right Right The rightmost pixel of the Region.
46212      * @param {Number} bottom Bottom The bottom pixel of the Region.
46213      * @param {Number} left Left The leftmost pixel of the Region.
46214      */
46215     constructor : function(t, r, b, l) {
46216         var me = this;
46217         me.y = me.top = me[1] = t;
46218         me.right = r;
46219         me.bottom = b;
46220         me.x = me.left = me[0] = l;
46221     },
46222
46223     /**
46224      * Checks if this region completely contains the region that is passed in.
46225      * @param {Ext.util.Region} region
46226      * @return {Boolean}
46227      */
46228     contains : function(region) {
46229         var me = this;
46230         return (region.x >= me.x &&
46231                 region.right <= me.right &&
46232                 region.y >= me.y &&
46233                 region.bottom <= me.bottom);
46234
46235     },
46236
46237     /**
46238      * Checks if this region intersects the region passed in.
46239      * @param {Ext.util.Region} region
46240      * @return {Ext.util.Region/Boolean} Returns the intersected region or false if there is no intersection.
46241      */
46242     intersect : function(region) {
46243         var me = this,
46244             t = Math.max(me.y, region.y),
46245             r = Math.min(me.right, region.right),
46246             b = Math.min(me.bottom, region.bottom),
46247             l = Math.max(me.x, region.x);
46248
46249         if (b > t && r > l) {
46250             return new this.self(t, r, b, l);
46251         }
46252         else {
46253             return false;
46254         }
46255     },
46256
46257     /**
46258      * Returns the smallest region that contains the current AND targetRegion.
46259      * @param {Ext.util.Region} region
46260      * @return {Ext.util.Region} a new region
46261      */
46262     union : function(region) {
46263         var me = this,
46264             t = Math.min(me.y, region.y),
46265             r = Math.max(me.right, region.right),
46266             b = Math.max(me.bottom, region.bottom),
46267             l = Math.min(me.x, region.x);
46268
46269         return new this.self(t, r, b, l);
46270     },
46271
46272     /**
46273      * Modifies the current region to be constrained to the targetRegion.
46274      * @param {Ext.util.Region} targetRegion
46275      * @return {Ext.util.Region} this
46276      */
46277     constrainTo : function(r) {
46278         var me = this,
46279             constrain = Ext.Number.constrain;
46280         me.top = me.y = constrain(me.top, r.y, r.bottom);
46281         me.bottom = constrain(me.bottom, r.y, r.bottom);
46282         me.left = me.x = constrain(me.left, r.x, r.right);
46283         me.right = constrain(me.right, r.x, r.right);
46284         return me;
46285     },
46286
46287     /**
46288      * Modifies the current region to be adjusted by offsets.
46289      * @param {Number} top top offset
46290      * @param {Number} right right offset
46291      * @param {Number} bottom bottom offset
46292      * @param {Number} left left offset
46293      * @return {Ext.util.Region} this
46294      */
46295     adjust : function(t, r, b, l) {
46296         var me = this;
46297         me.top = me.y += t;
46298         me.left = me.x += l;
46299         me.right += r;
46300         me.bottom += b;
46301         return me;
46302     },
46303
46304     /**
46305      * Get the offset amount of a point outside the region
46306      * @param {String} [axis]
46307      * @param {Ext.util.Point} [p] the point
46308      * @return {Ext.util.Offset}
46309      */
46310     getOutOfBoundOffset: function(axis, p) {
46311         if (!Ext.isObject(axis)) {
46312             if (axis == 'x') {
46313                 return this.getOutOfBoundOffsetX(p);
46314             } else {
46315                 return this.getOutOfBoundOffsetY(p);
46316             }
46317         } else {
46318             p = axis;
46319             var d = Ext.create('Ext.util.Offset');
46320             d.x = this.getOutOfBoundOffsetX(p.x);
46321             d.y = this.getOutOfBoundOffsetY(p.y);
46322             return d;
46323         }
46324
46325     },
46326
46327     /**
46328      * Get the offset amount on the x-axis
46329      * @param {Number} p the offset
46330      * @return {Number}
46331      */
46332     getOutOfBoundOffsetX: function(p) {
46333         if (p <= this.x) {
46334             return this.x - p;
46335         } else if (p >= this.right) {
46336             return this.right - p;
46337         }
46338
46339         return 0;
46340     },
46341
46342     /**
46343      * Get the offset amount on the y-axis
46344      * @param {Number} p the offset
46345      * @return {Number}
46346      */
46347     getOutOfBoundOffsetY: function(p) {
46348         if (p <= this.y) {
46349             return this.y - p;
46350         } else if (p >= this.bottom) {
46351             return this.bottom - p;
46352         }
46353
46354         return 0;
46355     },
46356
46357     /**
46358      * Check whether the point / offset is out of bound
46359      * @param {String} [axis]
46360      * @param {Ext.util.Point/Number} [p] the point / offset
46361      * @return {Boolean}
46362      */
46363     isOutOfBound: function(axis, p) {
46364         if (!Ext.isObject(axis)) {
46365             if (axis == 'x') {
46366                 return this.isOutOfBoundX(p);
46367             } else {
46368                 return this.isOutOfBoundY(p);
46369             }
46370         } else {
46371             p = axis;
46372             return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
46373         }
46374     },
46375
46376     /**
46377      * Check whether the offset is out of bound in the x-axis
46378      * @param {Number} p the offset
46379      * @return {Boolean}
46380      */
46381     isOutOfBoundX: function(p) {
46382         return (p < this.x || p > this.right);
46383     },
46384
46385     /**
46386      * Check whether the offset is out of bound in the y-axis
46387      * @param {Number} p the offset
46388      * @return {Boolean}
46389      */
46390     isOutOfBoundY: function(p) {
46391         return (p < this.y || p > this.bottom);
46392     },
46393
46394     /**
46395      * Restrict a point within the region by a certain factor.
46396      * @param {String} [axis]
46397      * @param {Ext.util.Point/Ext.util.Offset/Object} [p]
46398      * @param {Number} [factor]
46399      * @return {Ext.util.Point/Ext.util.Offset/Object/Number}
46400      * @private
46401      */
46402     restrict: function(axis, p, factor) {
46403         if (Ext.isObject(axis)) {
46404             var newP;
46405
46406             factor = p;
46407             p = axis;
46408
46409             if (p.copy) {
46410                 newP = p.copy();
46411             }
46412             else {
46413                 newP = {
46414                     x: p.x,
46415                     y: p.y
46416                 };
46417             }
46418
46419             newP.x = this.restrictX(p.x, factor);
46420             newP.y = this.restrictY(p.y, factor);
46421             return newP;
46422         } else {
46423             if (axis == 'x') {
46424                 return this.restrictX(p, factor);
46425             } else {
46426                 return this.restrictY(p, factor);
46427             }
46428         }
46429     },
46430
46431     /**
46432      * Restrict an offset within the region by a certain factor, on the x-axis
46433      * @param {Number} p
46434      * @param {Number} [factor=1] The factor.
46435      * @return {Number}
46436      * @private
46437      */
46438     restrictX : function(p, factor) {
46439         if (!factor) {
46440             factor = 1;
46441         }
46442
46443         if (p <= this.x) {
46444             p -= (p - this.x) * factor;
46445         }
46446         else if (p >= this.right) {
46447             p -= (p - this.right) * factor;
46448         }
46449         return p;
46450     },
46451
46452     /**
46453      * Restrict an offset within the region by a certain factor, on the y-axis
46454      * @param {Number} p
46455      * @param {Number} [factor] The factor, defaults to 1
46456      * @return {Number}
46457      * @private
46458      */
46459     restrictY : function(p, factor) {
46460         if (!factor) {
46461             factor = 1;
46462         }
46463
46464         if (p <= this.y) {
46465             p -= (p - this.y) * factor;
46466         }
46467         else if (p >= this.bottom) {
46468             p -= (p - this.bottom) * factor;
46469         }
46470         return p;
46471     },
46472
46473     /**
46474      * Get the width / height of this region
46475      * @return {Object} an object with width and height properties
46476      * @private
46477      */
46478     getSize: function() {
46479         return {
46480             width: this.right - this.x,
46481             height: this.bottom - this.y
46482         };
46483     },
46484
46485     /**
46486      * Create a copy of this Region.
46487      * @return {Ext.util.Region}
46488      */
46489     copy: function() {
46490         return new this.self(this.y, this.right, this.bottom, this.x);
46491     },
46492
46493     /**
46494      * Copy the values of another Region to this Region
46495      * @param {Ext.util.Region} p The region to copy from.
46496      * @return {Ext.util.Region} This Region
46497      */
46498     copyFrom: function(p) {
46499         var me = this;
46500         me.top = me.y = me[1] = p.y;
46501         me.right = p.right;
46502         me.bottom = p.bottom;
46503         me.left = me.x = me[0] = p.x;
46504
46505         return this;
46506     },
46507
46508     /*
46509      * Dump this to an eye-friendly string, great for debugging
46510      * @return {String}
46511      */
46512     toString: function() {
46513         return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
46514     },
46515
46516     /**
46517      * Translate this region by the given offset amount
46518      * @param {Ext.util.Offset/Object} x Object containing the `x` and `y` properties.
46519      * Or the x value is using the two argument form.
46520      * @param {Number} y The y value unless using an Offset object.
46521      * @return {Ext.util.Region} this This Region
46522      */
46523     translateBy: function(x, y) {
46524         if (arguments.length == 1) {
46525             y = x.y;
46526             x = x.x;
46527         }
46528         var me = this;
46529         me.top = me.y += y;
46530         me.right += x;
46531         me.bottom += y;
46532         me.left = me.x += x;
46533
46534         return me;
46535     },
46536
46537     /**
46538      * Round all the properties of this region
46539      * @return {Ext.util.Region} this This Region
46540      */
46541     round: function() {
46542         var me = this;
46543         me.top = me.y = Math.round(me.y);
46544         me.right = Math.round(me.right);
46545         me.bottom = Math.round(me.bottom);
46546         me.left = me.x = Math.round(me.x);
46547
46548         return me;
46549     },
46550
46551     /**
46552      * Check whether this region is equivalent to the given region
46553      * @param {Ext.util.Region} region The region to compare with
46554      * @return {Boolean}
46555      */
46556     equals: function(region) {
46557         return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left);
46558     }
46559 });
46560
46561 /*
46562  * This is a derivative of the similarly named class in the YUI Library.
46563  * The original license:
46564  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
46565  * Code licensed under the BSD License:
46566  * http://developer.yahoo.net/yui/license.txt
46567  */
46568
46569
46570 /**
46571  * @class Ext.dd.DragDropManager
46572  * DragDropManager is a singleton that tracks the element interaction for
46573  * all DragDrop items in the window.  Generally, you will not call
46574  * this class directly, but it does have helper methods that could
46575  * be useful in your DragDrop implementations.
46576  * @singleton
46577  */
46578 Ext.define('Ext.dd.DragDropManager', {
46579     singleton: true,
46580
46581     requires: ['Ext.util.Region'],
46582
46583     uses: ['Ext.tip.QuickTipManager'],
46584
46585     // shorter ClassName, to save bytes and use internally
46586     alternateClassName: ['Ext.dd.DragDropMgr', 'Ext.dd.DDM'],
46587
46588     /**
46589      * Two dimensional Array of registered DragDrop objects.  The first
46590      * dimension is the DragDrop item group, the second the DragDrop
46591      * object.
46592      * @property ids
46593      * @type String[]
46594      * @private
46595      */
46596     ids: {},
46597
46598     /**
46599      * Array of element ids defined as drag handles.  Used to determine
46600      * if the element that generated the mousedown event is actually the
46601      * handle and not the html element itself.
46602      * @property handleIds
46603      * @type String[]
46604      * @private
46605      */
46606     handleIds: {},
46607
46608     /**
46609      * the DragDrop object that is currently being dragged
46610      * @property {Ext.dd.DragDrop} dragCurrent
46611      * @private
46612      **/
46613     dragCurrent: null,
46614
46615     /**
46616      * the DragDrop object(s) that are being hovered over
46617      * @property {Ext.dd.DragDrop[]} dragOvers
46618      * @private
46619      */
46620     dragOvers: {},
46621
46622     /**
46623      * the X distance between the cursor and the object being dragged
46624      * @property deltaX
46625      * @type Number
46626      * @private
46627      */
46628     deltaX: 0,
46629
46630     /**
46631      * the Y distance between the cursor and the object being dragged
46632      * @property deltaY
46633      * @type Number
46634      * @private
46635      */
46636     deltaY: 0,
46637
46638     /**
46639      * Flag to determine if we should prevent the default behavior of the
46640      * events we define. By default this is true, but this can be set to
46641      * false if you need the default behavior (not recommended)
46642      * @property preventDefault
46643      * @type Boolean
46644      */
46645     preventDefault: true,
46646
46647     /**
46648      * Flag to determine if we should stop the propagation of the events
46649      * we generate. This is true by default but you may want to set it to
46650      * false if the html element contains other features that require the
46651      * mouse click.
46652      * @property stopPropagation
46653      * @type Boolean
46654      */
46655     stopPropagation: true,
46656
46657     /**
46658      * Internal flag that is set to true when drag and drop has been
46659      * intialized
46660      * @property initialized
46661      * @private
46662      */
46663     initialized: false,
46664
46665     /**
46666      * All drag and drop can be disabled.
46667      * @property locked
46668      * @private
46669      */
46670     locked: false,
46671
46672     /**
46673      * Called the first time an element is registered.
46674      * @method init
46675      * @private
46676      */
46677     init: function() {
46678         this.initialized = true;
46679     },
46680
46681     /**
46682      * In point mode, drag and drop interaction is defined by the
46683      * location of the cursor during the drag/drop
46684      * @property POINT
46685      * @type Number
46686      */
46687     POINT: 0,
46688
46689     /**
46690      * In intersect mode, drag and drop interaction is defined by the
46691      * overlap of two or more drag and drop objects.
46692      * @property INTERSECT
46693      * @type Number
46694      */
46695     INTERSECT: 1,
46696
46697     /**
46698      * The current drag and drop mode.  Default: POINT
46699      * @property mode
46700      * @type Number
46701      */
46702     mode: 0,
46703
46704     /**
46705      * Runs method on all drag and drop objects
46706      * @method _execOnAll
46707      * @private
46708      */
46709     _execOnAll: function(sMethod, args) {
46710         for (var i in this.ids) {
46711             for (var j in this.ids[i]) {
46712                 var oDD = this.ids[i][j];
46713                 if (! this.isTypeOfDD(oDD)) {
46714                     continue;
46715                 }
46716                 oDD[sMethod].apply(oDD, args);
46717             }
46718         }
46719     },
46720
46721     /**
46722      * Drag and drop initialization.  Sets up the global event handlers
46723      * @method _onLoad
46724      * @private
46725      */
46726     _onLoad: function() {
46727
46728         this.init();
46729
46730         var Event = Ext.EventManager;
46731         Event.on(document, "mouseup",   this.handleMouseUp, this, true);
46732         Event.on(document, "mousemove", this.handleMouseMove, this, true);
46733         Event.on(window,   "unload",    this._onUnload, this, true);
46734         Event.on(window,   "resize",    this._onResize, this, true);
46735         // Event.on(window,   "mouseout",    this._test);
46736
46737     },
46738
46739     /**
46740      * Reset constraints on all drag and drop objs
46741      * @method _onResize
46742      * @private
46743      */
46744     _onResize: function(e) {
46745         this._execOnAll("resetConstraints", []);
46746     },
46747
46748     /**
46749      * Lock all drag and drop functionality
46750      * @method lock
46751      */
46752     lock: function() { this.locked = true; },
46753
46754     /**
46755      * Unlock all drag and drop functionality
46756      * @method unlock
46757      */
46758     unlock: function() { this.locked = false; },
46759
46760     /**
46761      * Is drag and drop locked?
46762      * @method isLocked
46763      * @return {Boolean} True if drag and drop is locked, false otherwise.
46764      */
46765     isLocked: function() { return this.locked; },
46766
46767     /**
46768      * Location cache that is set for all drag drop objects when a drag is
46769      * initiated, cleared when the drag is finished.
46770      * @property locationCache
46771      * @private
46772      */
46773     locationCache: {},
46774
46775     /**
46776      * Set useCache to false if you want to force object the lookup of each
46777      * drag and drop linked element constantly during a drag.
46778      * @property useCache
46779      * @type Boolean
46780      */
46781     useCache: true,
46782
46783     /**
46784      * The number of pixels that the mouse needs to move after the
46785      * mousedown before the drag is initiated.  Default=3;
46786      * @property clickPixelThresh
46787      * @type Number
46788      */
46789     clickPixelThresh: 3,
46790
46791     /**
46792      * The number of milliseconds after the mousedown event to initiate the
46793      * drag if we don't get a mouseup event. Default=350
46794      * @property clickTimeThresh
46795      * @type Number
46796      */
46797     clickTimeThresh: 350,
46798
46799     /**
46800      * Flag that indicates that either the drag pixel threshold or the
46801      * mousdown time threshold has been met
46802      * @property dragThreshMet
46803      * @type Boolean
46804      * @private
46805      */
46806     dragThreshMet: false,
46807
46808     /**
46809      * Timeout used for the click time threshold
46810      * @property clickTimeout
46811      * @type Object
46812      * @private
46813      */
46814     clickTimeout: null,
46815
46816     /**
46817      * The X position of the mousedown event stored for later use when a
46818      * drag threshold is met.
46819      * @property startX
46820      * @type Number
46821      * @private
46822      */
46823     startX: 0,
46824
46825     /**
46826      * The Y position of the mousedown event stored for later use when a
46827      * drag threshold is met.
46828      * @property startY
46829      * @type Number
46830      * @private
46831      */
46832     startY: 0,
46833
46834     /**
46835      * Each DragDrop instance must be registered with the DragDropManager.
46836      * This is executed in DragDrop.init()
46837      * @method regDragDrop
46838      * @param {Ext.dd.DragDrop} oDD the DragDrop object to register
46839      * @param {String} sGroup the name of the group this element belongs to
46840      */
46841     regDragDrop: function(oDD, sGroup) {
46842         if (!this.initialized) { this.init(); }
46843
46844         if (!this.ids[sGroup]) {
46845             this.ids[sGroup] = {};
46846         }
46847         this.ids[sGroup][oDD.id] = oDD;
46848     },
46849
46850     /**
46851      * Removes the supplied dd instance from the supplied group. Executed
46852      * by DragDrop.removeFromGroup, so don't call this function directly.
46853      * @method removeDDFromGroup
46854      * @private
46855      */
46856     removeDDFromGroup: function(oDD, sGroup) {
46857         if (!this.ids[sGroup]) {
46858             this.ids[sGroup] = {};
46859         }
46860
46861         var obj = this.ids[sGroup];
46862         if (obj && obj[oDD.id]) {
46863             delete obj[oDD.id];
46864         }
46865     },
46866
46867     /**
46868      * Unregisters a drag and drop item.  This is executed in
46869      * DragDrop.unreg, use that method instead of calling this directly.
46870      * @method _remove
46871      * @private
46872      */
46873     _remove: function(oDD) {
46874         for (var g in oDD.groups) {
46875             if (g && this.ids[g] && this.ids[g][oDD.id]) {
46876                 delete this.ids[g][oDD.id];
46877             }
46878         }
46879         delete this.handleIds[oDD.id];
46880     },
46881
46882     /**
46883      * Each DragDrop handle element must be registered.  This is done
46884      * automatically when executing DragDrop.setHandleElId()
46885      * @method regHandle
46886      * @param {String} sDDId the DragDrop id this element is a handle for
46887      * @param {String} sHandleId the id of the element that is the drag
46888      * handle
46889      */
46890     regHandle: function(sDDId, sHandleId) {
46891         if (!this.handleIds[sDDId]) {
46892             this.handleIds[sDDId] = {};
46893         }
46894         this.handleIds[sDDId][sHandleId] = sHandleId;
46895     },
46896
46897     /**
46898      * Utility function to determine if a given element has been
46899      * registered as a drag drop item.
46900      * @method isDragDrop
46901      * @param {String} id the element id to check
46902      * @return {Boolean} true if this element is a DragDrop item,
46903      * false otherwise
46904      */
46905     isDragDrop: function(id) {
46906         return ( this.getDDById(id) ) ? true : false;
46907     },
46908
46909     /**
46910      * Returns the drag and drop instances that are in all groups the
46911      * passed in instance belongs to.
46912      * @method getRelated
46913      * @param {Ext.dd.DragDrop} p_oDD the obj to get related data for
46914      * @param {Boolean} bTargetsOnly if true, only return targetable objs
46915      * @return {Ext.dd.DragDrop[]} the related instances
46916      */
46917     getRelated: function(p_oDD, bTargetsOnly) {
46918         var oDDs = [];
46919         for (var i in p_oDD.groups) {
46920             for (var j in this.ids[i]) {
46921                 var dd = this.ids[i][j];
46922                 if (! this.isTypeOfDD(dd)) {
46923                     continue;
46924                 }
46925                 if (!bTargetsOnly || dd.isTarget) {
46926                     oDDs[oDDs.length] = dd;
46927                 }
46928             }
46929         }
46930
46931         return oDDs;
46932     },
46933
46934     /**
46935      * Returns true if the specified dd target is a legal target for
46936      * the specifice drag obj
46937      * @method isLegalTarget
46938      * @param {Ext.dd.DragDrop} oDD the drag obj
46939      * @param {Ext.dd.DragDrop} oTargetDD the target
46940      * @return {Boolean} true if the target is a legal target for the
46941      * dd obj
46942      */
46943     isLegalTarget: function (oDD, oTargetDD) {
46944         var targets = this.getRelated(oDD, true);
46945         for (var i=0, len=targets.length;i<len;++i) {
46946             if (targets[i].id == oTargetDD.id) {
46947                 return true;
46948             }
46949         }
46950
46951         return false;
46952     },
46953
46954     /**
46955      * My goal is to be able to transparently determine if an object is
46956      * typeof DragDrop, and the exact subclass of DragDrop.  typeof
46957      * returns "object", oDD.constructor.toString() always returns
46958      * "DragDrop" and not the name of the subclass.  So for now it just
46959      * evaluates a well-known variable in DragDrop.
46960      * @method isTypeOfDD
46961      * @param {Object} the object to evaluate
46962      * @return {Boolean} true if typeof oDD = DragDrop
46963      */
46964     isTypeOfDD: function (oDD) {
46965         return (oDD && oDD.__ygDragDrop);
46966     },
46967
46968     /**
46969      * Utility function to determine if a given element has been
46970      * registered as a drag drop handle for the given Drag Drop object.
46971      * @method isHandle
46972      * @param {String} id the element id to check
46973      * @return {Boolean} true if this element is a DragDrop handle, false
46974      * otherwise
46975      */
46976     isHandle: function(sDDId, sHandleId) {
46977         return ( this.handleIds[sDDId] &&
46978                         this.handleIds[sDDId][sHandleId] );
46979     },
46980
46981     /**
46982      * Returns the DragDrop instance for a given id
46983      * @method getDDById
46984      * @param {String} id the id of the DragDrop object
46985      * @return {Ext.dd.DragDrop} the drag drop object, null if it is not found
46986      */
46987     getDDById: function(id) {
46988         for (var i in this.ids) {
46989             if (this.ids[i][id]) {
46990                 return this.ids[i][id];
46991             }
46992         }
46993         return null;
46994     },
46995
46996     /**
46997      * Fired after a registered DragDrop object gets the mousedown event.
46998      * Sets up the events required to track the object being dragged
46999      * @method handleMouseDown
47000      * @param {Event} e the event
47001      * @param {Ext.dd.DragDrop} oDD the DragDrop object being dragged
47002      * @private
47003      */
47004     handleMouseDown: function(e, oDD) {
47005         if(Ext.tip.QuickTipManager){
47006             Ext.tip.QuickTipManager.ddDisable();
47007         }
47008         if(this.dragCurrent){
47009             // the original browser mouseup wasn't handled (e.g. outside FF browser window)
47010             // so clean up first to avoid breaking the next drag
47011             this.handleMouseUp(e);
47012         }
47013
47014         this.currentTarget = e.getTarget();
47015         this.dragCurrent = oDD;
47016
47017         var el = oDD.getEl();
47018
47019         // track start position
47020         this.startX = e.getPageX();
47021         this.startY = e.getPageY();
47022
47023         this.deltaX = this.startX - el.offsetLeft;
47024         this.deltaY = this.startY - el.offsetTop;
47025
47026         this.dragThreshMet = false;
47027
47028         this.clickTimeout = setTimeout(
47029                 function() {
47030                     var DDM = Ext.dd.DragDropManager;
47031                     DDM.startDrag(DDM.startX, DDM.startY);
47032                 },
47033                 this.clickTimeThresh );
47034     },
47035
47036     /**
47037      * Fired when either the drag pixel threshol or the mousedown hold
47038      * time threshold has been met.
47039      * @method startDrag
47040      * @param {Number} x the X position of the original mousedown
47041      * @param {Number} y the Y position of the original mousedown
47042      */
47043     startDrag: function(x, y) {
47044         clearTimeout(this.clickTimeout);
47045         if (this.dragCurrent) {
47046             this.dragCurrent.b4StartDrag(x, y);
47047             this.dragCurrent.startDrag(x, y);
47048         }
47049         this.dragThreshMet = true;
47050     },
47051
47052     /**
47053      * Internal function to handle the mouseup event.  Will be invoked
47054      * from the context of the document.
47055      * @method handleMouseUp
47056      * @param {Event} e the event
47057      * @private
47058      */
47059     handleMouseUp: function(e) {
47060
47061         if(Ext.tip && Ext.tip.QuickTipManager){
47062             Ext.tip.QuickTipManager.ddEnable();
47063         }
47064         if (! this.dragCurrent) {
47065             return;
47066         }
47067
47068         clearTimeout(this.clickTimeout);
47069
47070         if (this.dragThreshMet) {
47071             this.fireEvents(e, true);
47072         } else {
47073         }
47074
47075         this.stopDrag(e);
47076
47077         this.stopEvent(e);
47078     },
47079
47080     /**
47081      * Utility to stop event propagation and event default, if these
47082      * features are turned on.
47083      * @method stopEvent
47084      * @param {Event} e the event as returned by this.getEvent()
47085      */
47086     stopEvent: function(e){
47087         if(this.stopPropagation) {
47088             e.stopPropagation();
47089         }
47090
47091         if (this.preventDefault) {
47092             e.preventDefault();
47093         }
47094     },
47095
47096     /**
47097      * Internal function to clean up event handlers after the drag
47098      * operation is complete
47099      * @method stopDrag
47100      * @param {Event} e the event
47101      * @private
47102      */
47103     stopDrag: function(e) {
47104         // Fire the drag end event for the item that was dragged
47105         if (this.dragCurrent) {
47106             if (this.dragThreshMet) {
47107                 this.dragCurrent.b4EndDrag(e);
47108                 this.dragCurrent.endDrag(e);
47109             }
47110
47111             this.dragCurrent.onMouseUp(e);
47112         }
47113
47114         this.dragCurrent = null;
47115         this.dragOvers = {};
47116     },
47117
47118     /**
47119      * Internal function to handle the mousemove event.  Will be invoked
47120      * from the context of the html element.
47121      *
47122      * @TODO figure out what we can do about mouse events lost when the
47123      * user drags objects beyond the window boundary.  Currently we can
47124      * detect this in internet explorer by verifying that the mouse is
47125      * down during the mousemove event.  Firefox doesn't give us the
47126      * button state on the mousemove event.
47127      * @method handleMouseMove
47128      * @param {Event} e the event
47129      * @private
47130      */
47131     handleMouseMove: function(e) {
47132         if (! this.dragCurrent) {
47133             return true;
47134         }
47135         // var button = e.which || e.button;
47136
47137         // check for IE mouseup outside of page boundary
47138         if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
47139             this.stopEvent(e);
47140             return this.handleMouseUp(e);
47141         }
47142
47143         if (!this.dragThreshMet) {
47144             var diffX = Math.abs(this.startX - e.getPageX());
47145             var diffY = Math.abs(this.startY - e.getPageY());
47146             if (diffX > this.clickPixelThresh ||
47147                         diffY > this.clickPixelThresh) {
47148                 this.startDrag(this.startX, this.startY);
47149             }
47150         }
47151
47152         if (this.dragThreshMet) {
47153             this.dragCurrent.b4Drag(e);
47154             this.dragCurrent.onDrag(e);
47155             if(!this.dragCurrent.moveOnly){
47156                 this.fireEvents(e, false);
47157             }
47158         }
47159
47160         this.stopEvent(e);
47161
47162         return true;
47163     },
47164
47165     /**
47166      * Iterates over all of the DragDrop elements to find ones we are
47167      * hovering over or dropping on
47168      * @method fireEvents
47169      * @param {Event} e the event
47170      * @param {Boolean} isDrop is this a drop op or a mouseover op?
47171      * @private
47172      */
47173     fireEvents: function(e, isDrop) {
47174         var dc = this.dragCurrent;
47175
47176         // If the user did the mouse up outside of the window, we could
47177         // get here even though we have ended the drag.
47178         if (!dc || dc.isLocked()) {
47179             return;
47180         }
47181
47182         var pt = e.getPoint();
47183
47184         // cache the previous dragOver array
47185         var oldOvers = [];
47186
47187         var outEvts   = [];
47188         var overEvts  = [];
47189         var dropEvts  = [];
47190         var enterEvts = [];
47191
47192         // Check to see if the object(s) we were hovering over is no longer
47193         // being hovered over so we can fire the onDragOut event
47194         for (var i in this.dragOvers) {
47195
47196             var ddo = this.dragOvers[i];
47197
47198             if (! this.isTypeOfDD(ddo)) {
47199                 continue;
47200             }
47201
47202             if (! this.isOverTarget(pt, ddo, this.mode)) {
47203                 outEvts.push( ddo );
47204             }
47205
47206             oldOvers[i] = true;
47207             delete this.dragOvers[i];
47208         }
47209
47210         for (var sGroup in dc.groups) {
47211
47212             if ("string" != typeof sGroup) {
47213                 continue;
47214             }
47215
47216             for (i in this.ids[sGroup]) {
47217                 var oDD = this.ids[sGroup][i];
47218                 if (! this.isTypeOfDD(oDD)) {
47219                     continue;
47220                 }
47221
47222                 if (oDD.isTarget && !oDD.isLocked() && ((oDD != dc) || (dc.ignoreSelf === false))) {
47223                     if (this.isOverTarget(pt, oDD, this.mode)) {
47224                         // look for drop interactions
47225                         if (isDrop) {
47226                             dropEvts.push( oDD );
47227                         // look for drag enter and drag over interactions
47228                         } else {
47229
47230                             // initial drag over: dragEnter fires
47231                             if (!oldOvers[oDD.id]) {
47232                                 enterEvts.push( oDD );
47233                             // subsequent drag overs: dragOver fires
47234                             } else {
47235                                 overEvts.push( oDD );
47236                             }
47237
47238                             this.dragOvers[oDD.id] = oDD;
47239                         }
47240                     }
47241                 }
47242             }
47243         }
47244
47245         if (this.mode) {
47246             if (outEvts.length) {
47247                 dc.b4DragOut(e, outEvts);
47248                 dc.onDragOut(e, outEvts);
47249             }
47250
47251             if (enterEvts.length) {
47252                 dc.onDragEnter(e, enterEvts);
47253             }
47254
47255             if (overEvts.length) {
47256                 dc.b4DragOver(e, overEvts);
47257                 dc.onDragOver(e, overEvts);
47258             }
47259
47260             if (dropEvts.length) {
47261                 dc.b4DragDrop(e, dropEvts);
47262                 dc.onDragDrop(e, dropEvts);
47263             }
47264
47265         } else {
47266             // fire dragout events
47267             var len = 0;
47268             for (i=0, len=outEvts.length; i<len; ++i) {
47269                 dc.b4DragOut(e, outEvts[i].id);
47270                 dc.onDragOut(e, outEvts[i].id);
47271             }
47272
47273             // fire enter events
47274             for (i=0,len=enterEvts.length; i<len; ++i) {
47275                 // dc.b4DragEnter(e, oDD.id);
47276                 dc.onDragEnter(e, enterEvts[i].id);
47277             }
47278
47279             // fire over events
47280             for (i=0,len=overEvts.length; i<len; ++i) {
47281                 dc.b4DragOver(e, overEvts[i].id);
47282                 dc.onDragOver(e, overEvts[i].id);
47283             }
47284
47285             // fire drop events
47286             for (i=0, len=dropEvts.length; i<len; ++i) {
47287                 dc.b4DragDrop(e, dropEvts[i].id);
47288                 dc.onDragDrop(e, dropEvts[i].id);
47289             }
47290
47291         }
47292
47293         // notify about a drop that did not find a target
47294         if (isDrop && !dropEvts.length) {
47295             dc.onInvalidDrop(e);
47296         }
47297
47298     },
47299
47300     /**
47301      * Helper function for getting the best match from the list of drag
47302      * and drop objects returned by the drag and drop events when we are
47303      * in INTERSECT mode.  It returns either the first object that the
47304      * cursor is over, or the object that has the greatest overlap with
47305      * the dragged element.
47306      * @method getBestMatch
47307      * @param  {Ext.dd.DragDrop[]} dds The array of drag and drop objects
47308      * targeted
47309      * @return {Ext.dd.DragDrop}       The best single match
47310      */
47311     getBestMatch: function(dds) {
47312         var winner = null;
47313         // Return null if the input is not what we expect
47314         //if (!dds || !dds.length || dds.length == 0) {
47315            // winner = null;
47316         // If there is only one item, it wins
47317         //} else if (dds.length == 1) {
47318
47319         var len = dds.length;
47320
47321         if (len == 1) {
47322             winner = dds[0];
47323         } else {
47324             // Loop through the targeted items
47325             for (var i=0; i<len; ++i) {
47326                 var dd = dds[i];
47327                 // If the cursor is over the object, it wins.  If the
47328                 // cursor is over multiple matches, the first one we come
47329                 // to wins.
47330                 if (dd.cursorIsOver) {
47331                     winner = dd;
47332                     break;
47333                 // Otherwise the object with the most overlap wins
47334                 } else {
47335                     if (!winner ||
47336                         winner.overlap.getArea() < dd.overlap.getArea()) {
47337                         winner = dd;
47338                     }
47339                 }
47340             }
47341         }
47342
47343         return winner;
47344     },
47345
47346     /**
47347      * Refreshes the cache of the top-left and bottom-right points of the
47348      * drag and drop objects in the specified group(s).  This is in the
47349      * format that is stored in the drag and drop instance, so typical
47350      * usage is:
47351      * <code>
47352      * Ext.dd.DragDropManager.refreshCache(ddinstance.groups);
47353      * </code>
47354      * Alternatively:
47355      * <code>
47356      * Ext.dd.DragDropManager.refreshCache({group1:true, group2:true});
47357      * </code>
47358      * @TODO this really should be an indexed array.  Alternatively this
47359      * method could accept both.
47360      * @method refreshCache
47361      * @param {Object} groups an associative array of groups to refresh
47362      */
47363     refreshCache: function(groups) {
47364         for (var sGroup in groups) {
47365             if ("string" != typeof sGroup) {
47366                 continue;
47367             }
47368             for (var i in this.ids[sGroup]) {
47369                 var oDD = this.ids[sGroup][i];
47370
47371                 if (this.isTypeOfDD(oDD)) {
47372                 // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
47373                     var loc = this.getLocation(oDD);
47374                     if (loc) {
47375                         this.locationCache[oDD.id] = loc;
47376                     } else {
47377                         delete this.locationCache[oDD.id];
47378                         // this will unregister the drag and drop object if
47379                         // the element is not in a usable state
47380                         // oDD.unreg();
47381                     }
47382                 }
47383             }
47384         }
47385     },
47386
47387     /**
47388      * This checks to make sure an element exists and is in the DOM.  The
47389      * main purpose is to handle cases where innerHTML is used to remove
47390      * drag and drop objects from the DOM.  IE provides an 'unspecified
47391      * error' when trying to access the offsetParent of such an element
47392      * @method verifyEl
47393      * @param {HTMLElement} el the element to check
47394      * @return {Boolean} true if the element looks usable
47395      */
47396     verifyEl: function(el) {
47397         if (el) {
47398             var parent;
47399             if(Ext.isIE){
47400                 try{
47401                     parent = el.offsetParent;
47402                 }catch(e){}
47403             }else{
47404                 parent = el.offsetParent;
47405             }
47406             if (parent) {
47407                 return true;
47408             }
47409         }
47410
47411         return false;
47412     },
47413
47414     /**
47415      * Returns a Region object containing the drag and drop element's position
47416      * and size, including the padding configured for it
47417      * @method getLocation
47418      * @param {Ext.dd.DragDrop} oDD the drag and drop object to get the location for.
47419      * @return {Ext.util.Region} a Region object representing the total area
47420      * the element occupies, including any padding
47421      * the instance is configured for.
47422      */
47423     getLocation: function(oDD) {
47424         if (! this.isTypeOfDD(oDD)) {
47425             return null;
47426         }
47427
47428         //delegate getLocation method to the
47429         //drag and drop target.
47430         if (oDD.getRegion) {
47431             return oDD.getRegion();
47432         }
47433
47434         var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
47435
47436         try {
47437             pos= Ext.Element.getXY(el);
47438         } catch (e) { }
47439
47440         if (!pos) {
47441             return null;
47442         }
47443
47444         x1 = pos[0];
47445         x2 = x1 + el.offsetWidth;
47446         y1 = pos[1];
47447         y2 = y1 + el.offsetHeight;
47448
47449         t = y1 - oDD.padding[0];
47450         r = x2 + oDD.padding[1];
47451         b = y2 + oDD.padding[2];
47452         l = x1 - oDD.padding[3];
47453
47454         return Ext.create('Ext.util.Region', t, r, b, l);
47455     },
47456
47457     /**
47458      * Checks the cursor location to see if it over the target
47459      * @method isOverTarget
47460      * @param {Ext.util.Point} pt The point to evaluate
47461      * @param {Ext.dd.DragDrop} oTarget the DragDrop object we are inspecting
47462      * @return {Boolean} true if the mouse is over the target
47463      * @private
47464      */
47465     isOverTarget: function(pt, oTarget, intersect) {
47466         // use cache if available
47467         var loc = this.locationCache[oTarget.id];
47468         if (!loc || !this.useCache) {
47469             loc = this.getLocation(oTarget);
47470             this.locationCache[oTarget.id] = loc;
47471
47472         }
47473
47474         if (!loc) {
47475             return false;
47476         }
47477
47478         oTarget.cursorIsOver = loc.contains( pt );
47479
47480         // DragDrop is using this as a sanity check for the initial mousedown
47481         // in this case we are done.  In POINT mode, if the drag obj has no
47482         // contraints, we are also done. Otherwise we need to evaluate the
47483         // location of the target as related to the actual location of the
47484         // dragged element.
47485         var dc = this.dragCurrent;
47486         if (!dc || !dc.getTargetCoord ||
47487                 (!intersect && !dc.constrainX && !dc.constrainY)) {
47488             return oTarget.cursorIsOver;
47489         }
47490
47491         oTarget.overlap = null;
47492
47493         // Get the current location of the drag element, this is the
47494         // location of the mouse event less the delta that represents
47495         // where the original mousedown happened on the element.  We
47496         // need to consider constraints and ticks as well.
47497         var pos = dc.getTargetCoord(pt.x, pt.y);
47498
47499         var el = dc.getDragEl();
47500         var curRegion = Ext.create('Ext.util.Region', pos.y,
47501                                                pos.x + el.offsetWidth,
47502                                                pos.y + el.offsetHeight,
47503                                                pos.x );
47504
47505         var overlap = curRegion.intersect(loc);
47506
47507         if (overlap) {
47508             oTarget.overlap = overlap;
47509             return (intersect) ? true : oTarget.cursorIsOver;
47510         } else {
47511             return false;
47512         }
47513     },
47514
47515     /**
47516      * unload event handler
47517      * @method _onUnload
47518      * @private
47519      */
47520     _onUnload: function(e, me) {
47521         Ext.dd.DragDropManager.unregAll();
47522     },
47523
47524     /**
47525      * Cleans up the drag and drop events and objects.
47526      * @method unregAll
47527      * @private
47528      */
47529     unregAll: function() {
47530
47531         if (this.dragCurrent) {
47532             this.stopDrag();
47533             this.dragCurrent = null;
47534         }
47535
47536         this._execOnAll("unreg", []);
47537
47538         for (var i in this.elementCache) {
47539             delete this.elementCache[i];
47540         }
47541
47542         this.elementCache = {};
47543         this.ids = {};
47544     },
47545
47546     /**
47547      * A cache of DOM elements
47548      * @property elementCache
47549      * @private
47550      */
47551     elementCache: {},
47552
47553     /**
47554      * Get the wrapper for the DOM element specified
47555      * @method getElWrapper
47556      * @param {String} id the id of the element to get
47557      * @return {Ext.dd.DragDropManager.ElementWrapper} the wrapped element
47558      * @private
47559      * @deprecated This wrapper isn't that useful
47560      */
47561     getElWrapper: function(id) {
47562         var oWrapper = this.elementCache[id];
47563         if (!oWrapper || !oWrapper.el) {
47564             oWrapper = this.elementCache[id] =
47565                 new this.ElementWrapper(Ext.getDom(id));
47566         }
47567         return oWrapper;
47568     },
47569
47570     /**
47571      * Returns the actual DOM element
47572      * @method getElement
47573      * @param {String} id the id of the elment to get
47574      * @return {Object} The element
47575      * @deprecated use Ext.lib.Ext.getDom instead
47576      */
47577     getElement: function(id) {
47578         return Ext.getDom(id);
47579     },
47580
47581     /**
47582      * Returns the style property for the DOM element (i.e.,
47583      * document.getElById(id).style)
47584      * @method getCss
47585      * @param {String} id the id of the elment to get
47586      * @return {Object} The style property of the element
47587      */
47588     getCss: function(id) {
47589         var el = Ext.getDom(id);
47590         return (el) ? el.style : null;
47591     },
47592
47593     /**
47594      * @class Ext.dd.DragDropManager.ElementWrapper
47595      * Inner class for cached elements
47596      * @private
47597      * @deprecated
47598      */
47599     ElementWrapper: function(el) {
47600         /**
47601          * The element
47602          * @property el
47603          */
47604         this.el = el || null;
47605         /**
47606          * The element id
47607          * @property id
47608          */
47609         this.id = this.el && el.id;
47610         /**
47611          * A reference to the style property
47612          * @property css
47613          */
47614         this.css = this.el && el.style;
47615     },
47616
47617     // The DragDropManager class continues
47618     /** @class Ext.dd.DragDropManager */
47619
47620     /**
47621      * Returns the X position of an html element
47622      * @param {HTMLElement} el the element for which to get the position
47623      * @return {Number} the X coordinate
47624      */
47625     getPosX: function(el) {
47626         return Ext.Element.getX(el);
47627     },
47628
47629     /**
47630      * Returns the Y position of an html element
47631      * @param {HTMLElement} el the element for which to get the position
47632      * @return {Number} the Y coordinate
47633      */
47634     getPosY: function(el) {
47635         return Ext.Element.getY(el);
47636     },
47637
47638     /**
47639      * Swap two nodes.  In IE, we use the native method, for others we
47640      * emulate the IE behavior
47641      * @param {HTMLElement} n1 the first node to swap
47642      * @param {HTMLElement} n2 the other node to swap
47643      */
47644     swapNode: function(n1, n2) {
47645         if (n1.swapNode) {
47646             n1.swapNode(n2);
47647         } else {
47648             var p = n2.parentNode;
47649             var s = n2.nextSibling;
47650
47651             if (s == n1) {
47652                 p.insertBefore(n1, n2);
47653             } else if (n2 == n1.nextSibling) {
47654                 p.insertBefore(n2, n1);
47655             } else {
47656                 n1.parentNode.replaceChild(n2, n1);
47657                 p.insertBefore(n1, s);
47658             }
47659         }
47660     },
47661
47662     /**
47663      * Returns the current scroll position
47664      * @private
47665      */
47666     getScroll: function () {
47667         var doc   = window.document,
47668             docEl = doc.documentElement,
47669             body  = doc.body,
47670             top   = 0,
47671             left  = 0;
47672
47673         if (Ext.isGecko4) {
47674             top  = window.scrollYOffset;
47675             left = window.scrollXOffset;
47676         } else {
47677             if (docEl && (docEl.scrollTop || docEl.scrollLeft)) {
47678                 top  = docEl.scrollTop;
47679                 left = docEl.scrollLeft;
47680             } else if (body) {
47681                 top  = body.scrollTop;
47682                 left = body.scrollLeft;
47683             }
47684         }
47685         return {
47686             top: top,
47687             left: left
47688         };
47689     },
47690
47691     /**
47692      * Returns the specified element style property
47693      * @param {HTMLElement} el          the element
47694      * @param {String}      styleProp   the style property
47695      * @return {String} The value of the style property
47696      */
47697     getStyle: function(el, styleProp) {
47698         return Ext.fly(el).getStyle(styleProp);
47699     },
47700
47701     /**
47702      * Gets the scrollTop
47703      * @return {Number} the document's scrollTop
47704      */
47705     getScrollTop: function () {
47706         return this.getScroll().top;
47707     },
47708
47709     /**
47710      * Gets the scrollLeft
47711      * @return {Number} the document's scrollTop
47712      */
47713     getScrollLeft: function () {
47714         return this.getScroll().left;
47715     },
47716
47717     /**
47718      * Sets the x/y position of an element to the location of the
47719      * target element.
47720      * @param {HTMLElement} moveEl      The element to move
47721      * @param {HTMLElement} targetEl    The position reference element
47722      */
47723     moveToEl: function (moveEl, targetEl) {
47724         var aCoord = Ext.Element.getXY(targetEl);
47725         Ext.Element.setXY(moveEl, aCoord);
47726     },
47727
47728     /**
47729      * Numeric array sort function
47730      * @param {Number} a
47731      * @param {Number} b
47732      * @returns {Number} positive, negative or 0
47733      */
47734     numericSort: function(a, b) {
47735         return (a - b);
47736     },
47737
47738     /**
47739      * Internal counter
47740      * @property {Number} _timeoutCount
47741      * @private
47742      */
47743     _timeoutCount: 0,
47744
47745     /**
47746      * Trying to make the load order less important.  Without this we get
47747      * an error if this file is loaded before the Event Utility.
47748      * @private
47749      */
47750     _addListeners: function() {
47751         if ( document ) {
47752             this._onLoad();
47753         } else {
47754             if (this._timeoutCount > 2000) {
47755             } else {
47756                 setTimeout(this._addListeners, 10);
47757                 if (document && document.body) {
47758                     this._timeoutCount += 1;
47759                 }
47760             }
47761         }
47762     },
47763
47764     /**
47765      * Recursively searches the immediate parent and all child nodes for
47766      * the handle element in order to determine wheter or not it was
47767      * clicked.
47768      * @param {HTMLElement} node the html element to inspect
47769      */
47770     handleWasClicked: function(node, id) {
47771         if (this.isHandle(id, node.id)) {
47772             return true;
47773         } else {
47774             // check to see if this is a text node child of the one we want
47775             var p = node.parentNode;
47776
47777             while (p) {
47778                 if (this.isHandle(id, p.id)) {
47779                     return true;
47780                 } else {
47781                     p = p.parentNode;
47782                 }
47783             }
47784         }
47785
47786         return false;
47787     }
47788 }, function() {
47789     this._addListeners();
47790 });
47791
47792 /**
47793  * @class Ext.layout.container.Box
47794  * @extends Ext.layout.container.Container
47795  * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
47796  */
47797
47798 Ext.define('Ext.layout.container.Box', {
47799
47800     /* Begin Definitions */
47801
47802     alias: ['layout.box'],
47803     extend: 'Ext.layout.container.Container',
47804     alternateClassName: 'Ext.layout.BoxLayout',
47805
47806     requires: [
47807         'Ext.layout.container.boxOverflow.None',
47808         'Ext.layout.container.boxOverflow.Menu',
47809         'Ext.layout.container.boxOverflow.Scroller',
47810         'Ext.util.Format',
47811         'Ext.dd.DragDropManager'
47812     ],
47813
47814     /* End Definitions */
47815
47816     /**
47817      * @cfg {Boolean/Number/Object} animate
47818      * <p>If truthy, child Component are <i>animated</i> into position whenever the Container
47819      * is layed out. If this option is numeric, it is used as the animation duration in milliseconds.</p>
47820      * <p>May be set as a property at any time.</p>
47821      */
47822
47823     /**
47824      * @cfg {Object} defaultMargins
47825      * <p>If the individual contained items do not have a <tt>margins</tt>
47826      * property specified or margin specified via CSS, the default margins from this property will be
47827      * applied to each item.</p>
47828      * <br><p>This property may be specified as an object containing margins
47829      * to apply in the format:</p><pre><code>
47830 {
47831     top: (top margin),
47832     right: (right margin),
47833     bottom: (bottom margin),
47834     left: (left margin)
47835 }</code></pre>
47836      * <p>This property may also be specified as a string containing
47837      * space-separated, numeric margin values. The order of the sides associated
47838      * with each value matches the way CSS processes margin values:</p>
47839      * <div class="mdetail-params"><ul>
47840      * <li>If there is only one value, it applies to all sides.</li>
47841      * <li>If there are two values, the top and bottom borders are set to the
47842      * first value and the right and left are set to the second.</li>
47843      * <li>If there are three values, the top is set to the first value, the left
47844      * and right are set to the second, and the bottom is set to the third.</li>
47845      * <li>If there are four values, they apply to the top, right, bottom, and
47846      * left, respectively.</li>
47847      * </ul></div>
47848      */
47849     defaultMargins: {
47850         top: 0,
47851         right: 0,
47852         bottom: 0,
47853         left: 0
47854     },
47855
47856     /**
47857      * @cfg {String} padding
47858      * <p>Sets the padding to be applied to all child items managed by this layout.</p>
47859      * <p>This property must be specified as a string containing
47860      * space-separated, numeric padding values. The order of the sides associated
47861      * with each value matches the way CSS processes padding values:</p>
47862      * <div class="mdetail-params"><ul>
47863      * <li>If there is only one value, it applies to all sides.</li>
47864      * <li>If there are two values, the top and bottom borders are set to the
47865      * first value and the right and left are set to the second.</li>
47866      * <li>If there are three values, the top is set to the first value, the left
47867      * and right are set to the second, and the bottom is set to the third.</li>
47868      * <li>If there are four values, they apply to the top, right, bottom, and
47869      * left, respectively.</li>
47870      * </ul></div>
47871      */
47872     padding: '0',
47873     // documented in subclasses
47874     pack: 'start',
47875
47876     /**
47877      * @cfg {String} pack
47878      * Controls how the child items of the container are packed together. Acceptable configuration values
47879      * for this property are:
47880      * <div class="mdetail-params"><ul>
47881      * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
47882      * <b>left</b> side of container</div></li>
47883      * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
47884      * <b>mid-width</b> of container</div></li>
47885      * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
47886      * side of container</div></li>
47887      * </ul></div>
47888      */
47889     /**
47890      * @cfg {Number} flex
47891      * This configuration option is to be applied to <b>child <tt>items</tt></b> of the container managed
47892      * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
47893      * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
47894      * a <tt>flex</tt> value specified.  Any child items that have either a <tt>flex = 0</tt> or
47895      * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
47896      */
47897
47898     type: 'box',
47899     scrollOffset: 0,
47900     itemCls: Ext.baseCSSPrefix + 'box-item',
47901     targetCls: Ext.baseCSSPrefix + 'box-layout-ct',
47902     innerCls: Ext.baseCSSPrefix + 'box-inner',
47903
47904     bindToOwnerCtContainer: true,
47905
47906     // availableSpaceOffset is used to adjust the availableWidth, typically used
47907     // to reserve space for a scrollbar
47908     availableSpaceOffset: 0,
47909
47910     // whether or not to reserve the availableSpaceOffset in layout calculations
47911     reserveOffset: true,
47912
47913     /**
47914      * @cfg {Boolean} shrinkToFit
47915      * True (the default) to allow fixed size components to shrink (limited to their
47916      * minimum size) to avoid overflow. False to preserve fixed sizes even if they cause
47917      * overflow.
47918      */
47919     shrinkToFit: true,
47920
47921     /**
47922      * @cfg {Boolean} clearInnerCtOnLayout
47923      */
47924     clearInnerCtOnLayout: false,
47925
47926     flexSortFn: function (a, b) {
47927         var maxParallelPrefix = 'max' + this.parallelPrefixCap,
47928             infiniteValue = Infinity;
47929         a = a.component[maxParallelPrefix] || infiniteValue;
47930         b = b.component[maxParallelPrefix] || infiniteValue;
47931         // IE 6/7 Don't like Infinity - Infinity...
47932         if (!isFinite(a) && !isFinite(b)) {
47933             return false;
47934         }
47935         return a - b;
47936     },
47937
47938     // Sort into *descending* order.
47939     minSizeSortFn: function(a, b) {
47940         return b.available - a.available;
47941     },
47942
47943     constructor: function(config) {
47944         var me = this;
47945
47946         me.callParent(arguments);
47947
47948         // The sort function needs access to properties in this, so must be bound.
47949         me.flexSortFn = Ext.Function.bind(me.flexSortFn, me);
47950
47951         me.initOverflowHandler();
47952     },
47953
47954     /**
47955      * @private
47956      * Returns the current size and positioning of the passed child item.
47957      * @param {Ext.Component} child The child Component to calculate the box for
47958      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
47959      */
47960     getChildBox: function(child) {
47961         child = child.el || this.owner.getComponent(child).el;
47962         var size = child.getBox(false, true);
47963         return {
47964             left: size.left,
47965             top: size.top,
47966             width: size.width,
47967             height: size.height
47968         };
47969     },
47970
47971     /**
47972      * @private
47973      * Calculates the size and positioning of the passed child item.
47974      * @param {Ext.Component} child The child Component to calculate the box for
47975      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
47976      */
47977     calculateChildBox: function(child) {
47978         var me = this,
47979             boxes = me.calculateChildBoxes(me.getVisibleItems(), me.getLayoutTargetSize()).boxes,
47980             ln = boxes.length,
47981             i = 0;
47982
47983         child = me.owner.getComponent(child);
47984         for (; i < ln; i++) {
47985             if (boxes[i].component === child) {
47986                 return boxes[i];
47987             }
47988         }
47989     },
47990
47991     /**
47992      * @private
47993      * Calculates the size and positioning of each item in the box. This iterates over all of the rendered,
47994      * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
47995      * returns meta data such as maxSize which are useful when resizing layout wrappers such as this.innerCt.
47996      * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
47997      * @param {Object} targetSize Object containing target size and height
47998      * @return {Object} Object containing box measurements for each child, plus meta data
47999      */
48000     calculateChildBoxes: function(visibleItems, targetSize) {
48001         var me = this,
48002             math = Math,
48003             mmax = math.max,
48004             infiniteValue = Infinity,
48005             undefinedValue,
48006
48007             parallelPrefix = me.parallelPrefix,
48008             parallelPrefixCap = me.parallelPrefixCap,
48009             perpendicularPrefix = me.perpendicularPrefix,
48010             perpendicularPrefixCap = me.perpendicularPrefixCap,
48011             parallelMinString = 'min' + parallelPrefixCap,
48012             perpendicularMinString = 'min' + perpendicularPrefixCap,
48013             perpendicularMaxString = 'max' + perpendicularPrefixCap,
48014
48015             parallelSize = targetSize[parallelPrefix] - me.scrollOffset,
48016             perpendicularSize = targetSize[perpendicularPrefix],
48017             padding = me.padding,
48018             parallelOffset = padding[me.parallelBefore],
48019             paddingParallel = parallelOffset + padding[me.parallelAfter],
48020             perpendicularOffset = padding[me.perpendicularLeftTop],
48021             paddingPerpendicular =  perpendicularOffset + padding[me.perpendicularRightBottom],
48022             availPerpendicularSize = mmax(0, perpendicularSize - paddingPerpendicular),
48023
48024             innerCtBorderWidth = me.innerCt.getBorderWidth(me.perpendicularLT + me.perpendicularRB),
48025
48026             isStart = me.pack == 'start',
48027             isCenter = me.pack == 'center',
48028             isEnd = me.pack == 'end',
48029
48030             constrain = Ext.Number.constrain,
48031             visibleCount = visibleItems.length,
48032             nonFlexSize = 0,
48033             totalFlex = 0,
48034             desiredSize = 0,
48035             minimumSize = 0,
48036             maxSize = 0,
48037             boxes = [],
48038             minSizes = [],
48039             calculatedWidth,
48040
48041             i, child, childParallel, childPerpendicular, childMargins, childSize, minParallel, tmpObj, shortfall,
48042             tooNarrow, availableSpace, minSize, item, length, itemIndex, box, oldSize, newSize, reduction, diff,
48043             flexedBoxes, remainingSpace, remainingFlex, flexedSize, parallelMargins, calcs, offset,
48044             perpendicularMargins, stretchSize;
48045
48046         //gather the total flex of all flexed items and the width taken up by fixed width items
48047         for (i = 0; i < visibleCount; i++) {
48048             child = visibleItems[i];
48049             childPerpendicular = child[perpendicularPrefix];
48050             if (!child.flex || !(me.align == 'stretch' || me.align == 'stretchmax')) {
48051                 if (child.componentLayout.initialized !== true) {
48052                     me.layoutItem(child);
48053                 }
48054             }
48055
48056             childMargins = child.margins;
48057             parallelMargins = childMargins[me.parallelBefore] + childMargins[me.parallelAfter];
48058
48059             // Create the box description object for this child item.
48060             tmpObj = {
48061                 component: child,
48062                 margins: childMargins
48063             };
48064
48065             // flex and not 'auto' width
48066             if (child.flex) {
48067                 totalFlex += child.flex;
48068                 childParallel = undefinedValue;
48069             }
48070             // Not flexed or 'auto' width or undefined width
48071             else {
48072                 if (!(child[parallelPrefix] && childPerpendicular)) {
48073                     childSize = child.getSize();
48074                 }
48075                 childParallel = child[parallelPrefix] || childSize[parallelPrefix];
48076                 childPerpendicular = childPerpendicular || childSize[perpendicularPrefix];
48077             }
48078
48079             nonFlexSize += parallelMargins + (childParallel || 0);
48080             desiredSize += parallelMargins + (child.flex ? child[parallelMinString] || 0 : childParallel);
48081             minimumSize += parallelMargins + (child[parallelMinString] || childParallel || 0);
48082
48083             // Max height for align - force layout of non-laid out subcontainers without a numeric height
48084             if (typeof childPerpendicular != 'number') {
48085                 // Clear any static sizing and revert to flow so we can get a proper measurement
48086                 // child['set' + perpendicularPrefixCap](null);
48087                 childPerpendicular = child['get' + perpendicularPrefixCap]();
48088             }
48089
48090             // Track the maximum perpendicular size for use by the stretch and stretchmax align config values.
48091             // Ensure that the tracked maximum perpendicular size takes into account child min[Width|Height] settings!
48092             maxSize = mmax(maxSize, mmax(childPerpendicular, child[perpendicularMinString]||0) + childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom]);
48093
48094             tmpObj[parallelPrefix] = childParallel || undefinedValue;
48095             tmpObj.dirtySize = child.componentLayout.lastComponentSize ? (tmpObj[parallelPrefix] !== child.componentLayout.lastComponentSize[parallelPrefix]) : false;
48096             tmpObj[perpendicularPrefix] = childPerpendicular || undefinedValue;
48097             boxes.push(tmpObj);
48098         }
48099
48100         // Only calculate parallel overflow indicators if we are not auto sizing
48101         if (!me.autoSize) {
48102             shortfall = desiredSize - parallelSize;
48103             tooNarrow = minimumSize > parallelSize;
48104         }
48105
48106         //the space available to the flexed items
48107         availableSpace = mmax(0, parallelSize - nonFlexSize - paddingParallel - (me.reserveOffset ? me.availableSpaceOffset : 0));
48108
48109         if (tooNarrow) {
48110             for (i = 0; i < visibleCount; i++) {
48111                 box = boxes[i];
48112                 minSize = visibleItems[i][parallelMinString] || visibleItems[i][parallelPrefix] || box[parallelPrefix];
48113                 box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
48114                 box[parallelPrefix] = minSize;
48115             }
48116         }
48117         else {
48118             //all flexed items should be sized to their minimum size, other items should be shrunk down until
48119             //the shortfall has been accounted for
48120             if (shortfall > 0) {
48121                 /*
48122                  * When we have a shortfall but are not tooNarrow, we need to shrink the width of each non-flexed item.
48123                  * Flexed items are immediately reduced to their minWidth and anything already at minWidth is ignored.
48124                  * The remaining items are collected into the minWidths array, which is later used to distribute the shortfall.
48125                  */
48126                 for (i = 0; i < visibleCount; i++) {
48127                     item = visibleItems[i];
48128                     minSize = item[parallelMinString] || 0;
48129
48130                     //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
48131                     //shrunk to their minSize because they're flexible and should be the first to lose size
48132                     if (item.flex) {
48133                         box = boxes[i];
48134                         box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
48135                         box[parallelPrefix] = minSize;
48136                     } else if (me.shrinkToFit) {
48137                         minSizes.push({
48138                             minSize: minSize,
48139                             available: boxes[i][parallelPrefix] - minSize,
48140                             index: i
48141                         });
48142                     }
48143                 }
48144
48145                 //sort by descending amount of width remaining before minWidth is reached
48146                 Ext.Array.sort(minSizes, me.minSizeSortFn);
48147
48148                 /*
48149                  * Distribute the shortfall (difference between total desired size of all items and actual size available)
48150                  * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
48151                  * smallest difference between their size and minSize first, so that if reducing the size by the average
48152                  * amount would make that item less than its minSize, we carry the remainder over to the next item.
48153                  */
48154                 for (i = 0, length = minSizes.length; i < length; i++) {
48155                     itemIndex = minSizes[i].index;
48156
48157                     if (itemIndex == undefinedValue) {
48158                         continue;
48159                     }
48160                     item = visibleItems[itemIndex];
48161                     minSize = minSizes[i].minSize;
48162
48163                     box = boxes[itemIndex];
48164                     oldSize = box[parallelPrefix];
48165                     newSize = mmax(minSize, oldSize - math.ceil(shortfall / (length - i)));
48166                     reduction = oldSize - newSize;
48167
48168                     box.dirtySize = box.dirtySize || box[parallelPrefix] != newSize;
48169                     box[parallelPrefix] = newSize;
48170                     shortfall -= reduction;
48171                 }
48172                 tooNarrow = (shortfall > 0);
48173             }
48174             else {
48175                 remainingSpace = availableSpace;
48176                 remainingFlex = totalFlex;
48177                 flexedBoxes = [];
48178
48179                 // Create an array containing *just the flexed boxes* for allocation of remainingSpace
48180                 for (i = 0; i < visibleCount; i++) {
48181                     child = visibleItems[i];
48182                     if (isStart && child.flex) {
48183                         flexedBoxes.push(boxes[Ext.Array.indexOf(visibleItems, child)]);
48184                     }
48185                 }
48186                 // The flexed boxes need to be sorted in ascending order of maxSize to work properly
48187                 // so that unallocated space caused by maxWidth being less than flexed width
48188                 // can be reallocated to subsequent flexed boxes.
48189                 Ext.Array.sort(flexedBoxes, me.flexSortFn);
48190
48191                 // Calculate the size of each flexed item, and attempt to set it.
48192                 for (i = 0; i < flexedBoxes.length; i++) {
48193                     calcs = flexedBoxes[i];
48194                     child = calcs.component;
48195                     childMargins = calcs.margins;
48196
48197                     flexedSize = math.ceil((child.flex / remainingFlex) * remainingSpace);
48198
48199                     // Implement maxSize and minSize check
48200                     flexedSize = Math.max(child['min' + parallelPrefixCap] || 0, math.min(child['max' + parallelPrefixCap] || infiniteValue, flexedSize));
48201
48202                     // Remaining space has already had all parallel margins subtracted from it, so just subtract consumed size
48203                     remainingSpace -= flexedSize;
48204                     remainingFlex -= child.flex;
48205
48206                     calcs.dirtySize = calcs.dirtySize || calcs[parallelPrefix] != flexedSize;
48207                     calcs[parallelPrefix] = flexedSize;
48208                 }
48209             }
48210         }
48211
48212         if (isCenter) {
48213             parallelOffset += availableSpace / 2;
48214         }
48215         else if (isEnd) {
48216             parallelOffset += availableSpace;
48217         }
48218
48219         // Fix for left and right docked Components in a dock component layout. This is for docked Headers and docked Toolbars.
48220         // Older Microsoft browsers do not size a position:absolute element's width to match its content.
48221         // So in this case, in the updateInnerCtSize method we may need to adjust the size of the owning Container's element explicitly based upon
48222         // the discovered max width. So here we put a calculatedWidth property in the metadata to facilitate this.
48223         if (me.owner.dock && (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) && !me.owner.width && me.direction == 'vertical') {
48224
48225             calculatedWidth = maxSize + me.owner.el.getPadding('lr') + me.owner.el.getBorderWidth('lr');
48226             if (me.owner.frameSize) {
48227                 calculatedWidth += me.owner.frameSize.left + me.owner.frameSize.right;
48228             }
48229             // If the owning element is not sized, calculate the available width to center or stretch in based upon maxSize
48230             availPerpendicularSize = Math.min(availPerpendicularSize, targetSize.width = maxSize + padding.left + padding.right);
48231         }
48232
48233         //finally, calculate the left and top position of each item
48234         for (i = 0; i < visibleCount; i++) {
48235             child = visibleItems[i];
48236             calcs = boxes[i];
48237
48238             childMargins = calcs.margins;
48239
48240             perpendicularMargins = childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom];
48241
48242             // Advance past the "before" margin
48243             parallelOffset += childMargins[me.parallelBefore];
48244
48245             calcs[me.parallelBefore] = parallelOffset;
48246             calcs[me.perpendicularLeftTop] = perpendicularOffset + childMargins[me.perpendicularLeftTop];
48247
48248             if (me.align == 'stretch') {
48249                 stretchSize = constrain(availPerpendicularSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
48250                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
48251                 calcs[perpendicularPrefix] = stretchSize;
48252             }
48253             else if (me.align == 'stretchmax') {
48254                 stretchSize = constrain(maxSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
48255                 calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
48256                 calcs[perpendicularPrefix] = stretchSize;
48257             }
48258             else if (me.align == me.alignCenteringString) {
48259                 // When calculating a centered position within the content box of the innerCt, the width of the borders must be subtracted from
48260                 // the size to yield the space available to center within.
48261                 // The updateInnerCtSize method explicitly adds the border widths to the set size of the innerCt.
48262                 diff = mmax(availPerpendicularSize, maxSize) - innerCtBorderWidth - calcs[perpendicularPrefix];
48263                 if (diff > 0) {
48264                     calcs[me.perpendicularLeftTop] = perpendicularOffset + Math.round(diff / 2);
48265                 }
48266             }
48267
48268             // Advance past the box size and the "after" margin
48269             parallelOffset += (calcs[parallelPrefix] || 0) + childMargins[me.parallelAfter];
48270         }
48271
48272         return {
48273             boxes: boxes,
48274             meta : {
48275                 calculatedWidth: calculatedWidth,
48276                 maxSize: maxSize,
48277                 nonFlexSize: nonFlexSize,
48278                 desiredSize: desiredSize,
48279                 minimumSize: minimumSize,
48280                 shortfall: shortfall,
48281                 tooNarrow: tooNarrow
48282             }
48283         };
48284     },
48285
48286     onRemove: function(comp){
48287         this.callParent(arguments);
48288         if (this.overflowHandler) {
48289             this.overflowHandler.onRemove(comp);
48290         }
48291     },
48292
48293     /**
48294      * @private
48295      */
48296     initOverflowHandler: function() {
48297         var handler = this.overflowHandler;
48298
48299         if (typeof handler == 'string') {
48300             handler = {
48301                 type: handler
48302             };
48303         }
48304
48305         var handlerType = 'None';
48306         if (handler && handler.type !== undefined) {
48307             handlerType = handler.type;
48308         }
48309
48310         var constructor = Ext.layout.container.boxOverflow[handlerType];
48311         if (constructor[this.type]) {
48312             constructor = constructor[this.type];
48313         }
48314
48315         this.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.' + handlerType, this, handler);
48316     },
48317
48318     /**
48319      * @private
48320      * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
48321      * when laying out
48322      */
48323     onLayout: function() {
48324         this.callParent();
48325         // Clear the innerCt size so it doesn't influence the child items.
48326         if (this.clearInnerCtOnLayout === true && this.adjustmentPass !== true) {
48327             this.innerCt.setSize(null, null);
48328         }
48329
48330         var me = this,
48331             targetSize = me.getLayoutTargetSize(),
48332             items = me.getVisibleItems(),
48333             calcs = me.calculateChildBoxes(items, targetSize),
48334             boxes = calcs.boxes,
48335             meta = calcs.meta,
48336             handler, method, results;
48337
48338         if (me.autoSize && calcs.meta.desiredSize) {
48339             targetSize[me.parallelPrefix] = calcs.meta.desiredSize;
48340         }
48341
48342         //invoke the overflow handler, if one is configured
48343         if (meta.shortfall > 0) {
48344             handler = me.overflowHandler;
48345             method = meta.tooNarrow ? 'handleOverflow': 'clearOverflow';
48346
48347             results = handler[method](calcs, targetSize);
48348
48349             if (results) {
48350                 if (results.targetSize) {
48351                     targetSize = results.targetSize;
48352                 }
48353
48354                 if (results.recalculate) {
48355                     items = me.getVisibleItems();
48356                     calcs = me.calculateChildBoxes(items, targetSize);
48357                     boxes = calcs.boxes;
48358                 }
48359             }
48360         } else {
48361             me.overflowHandler.clearOverflow();
48362         }
48363
48364         /**
48365          * @private
48366          * @property layoutTargetLastSize
48367          * @type Object
48368          * Private cache of the last measured size of the layout target. This should never be used except by
48369          * BoxLayout subclasses during their onLayout run.
48370          */
48371         me.layoutTargetLastSize = targetSize;
48372
48373         /**
48374          * @private
48375          * @property childBoxCache
48376          * @type Array
48377          * Array of the last calculated height, width, top and left positions of each visible rendered component
48378          * within the Box layout.
48379          */
48380         me.childBoxCache = calcs;
48381
48382         me.updateInnerCtSize(targetSize, calcs);
48383         me.updateChildBoxes(boxes);
48384         me.handleTargetOverflow(targetSize);
48385     },
48386     
48387     animCallback: Ext.emptyFn,
48388
48389     /**
48390      * Resizes and repositions each child component
48391      * @param {Object[]} boxes The box measurements
48392      */
48393     updateChildBoxes: function(boxes) {
48394         var me = this,
48395             i = 0,
48396             length = boxes.length,
48397             animQueue = [],
48398             dd = Ext.dd.DDM.getDDById(me.innerCt.id), // Any DD active on this layout's element (The BoxReorderer plugin does this.)
48399             oldBox, newBox, changed, comp, boxAnim, animCallback;
48400
48401         for (; i < length; i++) {
48402             newBox = boxes[i];
48403             comp = newBox.component;
48404
48405             // If a Component is being drag/dropped, skip positioning it.
48406             // Accomodate the BoxReorderer plugin: Its current dragEl must not be positioned by the layout
48407             if (dd && (dd.getDragEl() === comp.el.dom)) {
48408                 continue;
48409             }
48410
48411             changed = false;
48412
48413             oldBox = me.getChildBox(comp);
48414
48415             // If we are animating, we build up an array of Anim config objects, one for each
48416             // child Component which has any changed box properties. Those with unchanged
48417             // properties are not animated.
48418             if (me.animate) {
48419                 // Animate may be a config object containing callback.
48420                 animCallback = me.animate.callback || me.animate;
48421                 boxAnim = {
48422                     layoutAnimation: true,  // Component Target handler must use set*Calculated*Size
48423                     target: comp,
48424                     from: {},
48425                     to: {},
48426                     listeners: {}
48427                 };
48428                 // Only set from and to properties when there's a change.
48429                 // Perform as few Component setter methods as possible.
48430                 // Temporarily set the property values that we are not animating
48431                 // so that doComponentLayout does not auto-size them.
48432                 if (!isNaN(newBox.width) && (newBox.width != oldBox.width)) {
48433                     changed = true;
48434                     // boxAnim.from.width = oldBox.width;
48435                     boxAnim.to.width = newBox.width;
48436                 }
48437                 if (!isNaN(newBox.height) && (newBox.height != oldBox.height)) {
48438                     changed = true;
48439                     // boxAnim.from.height = oldBox.height;
48440                     boxAnim.to.height = newBox.height;
48441                 }
48442                 if (!isNaN(newBox.left) && (newBox.left != oldBox.left)) {
48443                     changed = true;
48444                     // boxAnim.from.left = oldBox.left;
48445                     boxAnim.to.left = newBox.left;
48446                 }
48447                 if (!isNaN(newBox.top) && (newBox.top != oldBox.top)) {
48448                     changed = true;
48449                     // boxAnim.from.top = oldBox.top;
48450                     boxAnim.to.top = newBox.top;
48451                 }
48452                 if (changed) {
48453                     animQueue.push(boxAnim);
48454                 }
48455             } else {
48456                 if (newBox.dirtySize) {
48457                     if (newBox.width !== oldBox.width || newBox.height !== oldBox.height) {
48458                         me.setItemSize(comp, newBox.width, newBox.height);
48459                     }
48460                 }
48461                 // Don't set positions to NaN
48462                 if (isNaN(newBox.left) || isNaN(newBox.top)) {
48463                     continue;
48464                 }
48465                 comp.setPosition(newBox.left, newBox.top);
48466             }
48467         }
48468
48469         // Kick off any queued animations
48470         length = animQueue.length;
48471         if (length) {
48472
48473             // A function which cleans up when a Component's animation is done.
48474             // The last one to finish calls the callback.
48475             var afterAnimate = function(anim) {
48476                 // When we've animated all changed boxes into position, clear our busy flag and call the callback.
48477                 length -= 1;
48478                 if (!length) {
48479                     me.animCallback(anim);
48480                     me.layoutBusy = false;
48481                     if (Ext.isFunction(animCallback)) {
48482                         animCallback();
48483                     }
48484                 }
48485             };
48486
48487             var beforeAnimate = function() {
48488                 me.layoutBusy = true;
48489             };
48490
48491             // Start each box animation off
48492             for (i = 0, length = animQueue.length; i < length; i++) {
48493                 boxAnim = animQueue[i];
48494
48495                 // Clean up the Component after. Clean up the *layout* after the last animation finishes
48496                 boxAnim.listeners.afteranimate = afterAnimate;
48497
48498                 // The layout is busy during animation, and may not be called, so set the flag when the first animation begins
48499                 if (!i) {
48500                     boxAnim.listeners.beforeanimate = beforeAnimate;
48501                 }
48502                 if (me.animate.duration) {
48503                     boxAnim.duration = me.animate.duration;
48504                 }
48505                 comp = boxAnim.target;
48506                 delete boxAnim.target;
48507                 // Stop any currently running animation
48508                 comp.stopAnimation();
48509                 comp.animate(boxAnim);
48510             }
48511         }
48512     },
48513
48514     /**
48515      * @private
48516      * Called by onRender just before the child components are sized and positioned. This resizes the innerCt
48517      * to make sure all child items fit within it. We call this before sizing the children because if our child
48518      * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them
48519      * again immediately afterwards, giving a performance hit.
48520      * Subclasses should provide an implementation.
48521      * @param {Object} currentSize The current height and width of the innerCt
48522      * @param {Object} calculations The new box calculations of all items to be laid out
48523      */
48524     updateInnerCtSize: function(tSize, calcs) {
48525         var me = this,
48526             mmax = Math.max,
48527             align = me.align,
48528             padding = me.padding,
48529             width = tSize.width,
48530             height = tSize.height,
48531             meta = calcs.meta,
48532             innerCtWidth,
48533             innerCtHeight;
48534
48535         if (me.direction == 'horizontal') {
48536             innerCtWidth = width;
48537             innerCtHeight = meta.maxSize + padding.top + padding.bottom + me.innerCt.getBorderWidth('tb');
48538
48539             if (align == 'stretch') {
48540                 innerCtHeight = height;
48541             }
48542             else if (align == 'middle') {
48543                 innerCtHeight = mmax(height, innerCtHeight);
48544             }
48545         } else {
48546             innerCtHeight = height;
48547             innerCtWidth = meta.maxSize + padding.left + padding.right + me.innerCt.getBorderWidth('lr');
48548
48549             if (align == 'stretch') {
48550                 innerCtWidth = width;
48551             }
48552             else if (align == 'center') {
48553                 innerCtWidth = mmax(width, innerCtWidth);
48554             }
48555         }
48556         me.getRenderTarget().setSize(innerCtWidth || undefined, innerCtHeight || undefined);
48557
48558         // If a calculated width has been found (and this only happens for auto-width vertical docked Components in old Microsoft browsers)
48559         // then, if the Component has not assumed the size of its content, set it to do so.
48560         if (meta.calculatedWidth && me.owner.el.getWidth() > meta.calculatedWidth) {
48561             me.owner.el.setWidth(meta.calculatedWidth);
48562         }
48563
48564         if (me.innerCt.dom.scrollTop) {
48565             me.innerCt.dom.scrollTop = 0;
48566         }
48567     },
48568
48569     /**
48570      * @private
48571      * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
48572      * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
48573      * target. Having a Box layout inside such a target is therefore not recommended.
48574      * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
48575      * @param {Ext.container.Container} container The container
48576      * @param {Ext.Element} target The target element
48577      * @return True if the layout overflowed, and was reflowed in a secondary onLayout call.
48578      */
48579     handleTargetOverflow: function(previousTargetSize) {
48580         var target = this.getTarget(),
48581             overflow = target.getStyle('overflow'),
48582             newTargetSize;
48583
48584         if (overflow && overflow != 'hidden' && !this.adjustmentPass) {
48585             newTargetSize = this.getLayoutTargetSize();
48586             if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height) {
48587                 this.adjustmentPass = true;
48588                 this.onLayout();
48589                 return true;
48590             }
48591         }
48592
48593         delete this.adjustmentPass;
48594     },
48595
48596     // private
48597     isValidParent : function(item, target, position) {
48598         // Note: Box layouts do not care about order within the innerCt element because it's an absolutely positioning layout
48599         // We only care whether the item is a direct child of the innerCt element.
48600         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
48601         return (itemEl && this.innerCt && itemEl.parentNode === this.innerCt.dom) || false;
48602     },
48603
48604     // Overridden method from AbstractContainer.
48605     // Used in the base AbstractLayout.beforeLayout method to render all items into.
48606     getRenderTarget: function() {
48607         if (!this.innerCt) {
48608             // the innerCt prevents wrapping and shuffling while the container is resizing
48609             this.innerCt = this.getTarget().createChild({
48610                 cls: this.innerCls,
48611                 role: 'presentation'
48612             });
48613             this.padding = Ext.util.Format.parseBox(this.padding);
48614         }
48615         return this.innerCt;
48616     },
48617
48618     // private
48619     renderItem: function(item, target) {
48620         this.callParent(arguments);
48621         var me = this,
48622             itemEl = item.getEl(),
48623             style = itemEl.dom.style,
48624             margins = item.margins || item.margin;
48625
48626         // Parse the item's margin/margins specification
48627         if (margins) {
48628             if (Ext.isString(margins) || Ext.isNumber(margins)) {
48629                 margins = Ext.util.Format.parseBox(margins);
48630             } else {
48631                 Ext.applyIf(margins, {top: 0, right: 0, bottom: 0, left: 0});
48632             }
48633         } else {
48634             margins = Ext.apply({}, me.defaultMargins);
48635         }
48636
48637         // Add any before/after CSS margins to the configured margins, and zero the CSS margins
48638         margins.top    += itemEl.getMargin('t');
48639         margins.right  += itemEl.getMargin('r');
48640         margins.bottom += itemEl.getMargin('b');
48641         margins.left   += itemEl.getMargin('l');
48642         margins.height  = margins.top  + margins.bottom;
48643         margins.width   = margins.left + margins.right;
48644         style.marginTop = style.marginRight = style.marginBottom = style.marginLeft = '0';
48645
48646         // Item must reference calculated margins.
48647         item.margins = margins;
48648     },
48649
48650     /**
48651      * @private
48652      */
48653     destroy: function() {
48654         Ext.destroy(this.innerCt, this.overflowHandler);
48655         this.callParent(arguments);
48656     }
48657 });
48658 /**
48659  * A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
48660  * space between child items containing a numeric `flex` configuration.
48661  *
48662  * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
48663  *
48664  *     @example
48665  *     Ext.create('Ext.Panel', {
48666  *         width: 500,
48667  *         height: 300,
48668  *         title: "HBoxLayout Panel",
48669  *         layout: {
48670  *             type: 'hbox',
48671  *             align: 'stretch'
48672  *         },
48673  *         renderTo: document.body,
48674  *         items: [{
48675  *             xtype: 'panel',
48676  *             title: 'Inner Panel One',
48677  *             flex: 2
48678  *         },{
48679  *             xtype: 'panel',
48680  *             title: 'Inner Panel Two',
48681  *             flex: 1
48682  *         },{
48683  *             xtype: 'panel',
48684  *             title: 'Inner Panel Three',
48685  *             flex: 1
48686  *         }]
48687  *     });
48688  */
48689 Ext.define('Ext.layout.container.HBox', {
48690
48691     /* Begin Definitions */
48692
48693     alias: ['layout.hbox'],
48694     extend: 'Ext.layout.container.Box',
48695     alternateClassName: 'Ext.layout.HBoxLayout',
48696
48697     /* End Definitions */
48698
48699     /**
48700      * @cfg {String} align
48701      * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
48702      *
48703      * - **top** : **Default** child items are aligned vertically at the **top** of the container
48704      * - **middle** : child items are aligned vertically in the **middle** of the container
48705      * - **stretch** : child items are stretched vertically to fill the height of the container
48706      * - **stretchmax** : child items are stretched vertically to the height of the largest item.
48707      */
48708     align: 'top', // top, middle, stretch, strechmax
48709
48710     //@private
48711     alignCenteringString: 'middle',
48712
48713     type : 'hbox',
48714
48715     direction: 'horizontal',
48716
48717     // When creating an argument list to setSize, use this order
48718     parallelSizeIndex: 0,
48719     perpendicularSizeIndex: 1,
48720
48721     parallelPrefix: 'width',
48722     parallelPrefixCap: 'Width',
48723     parallelLT: 'l',
48724     parallelRB: 'r',
48725     parallelBefore: 'left',
48726     parallelBeforeCap: 'Left',
48727     parallelAfter: 'right',
48728     parallelPosition: 'x',
48729
48730     perpendicularPrefix: 'height',
48731     perpendicularPrefixCap: 'Height',
48732     perpendicularLT: 't',
48733     perpendicularRB: 'b',
48734     perpendicularLeftTop: 'top',
48735     perpendicularRightBottom: 'bottom',
48736     perpendicularPosition: 'y',
48737     configureItem: function(item) {
48738         if (item.flex) {
48739             item.layoutManagedWidth = 1;
48740         } else {
48741             item.layoutManagedWidth = 2;
48742         }
48743
48744         if (this.align === 'stretch' || this.align === 'stretchmax') {
48745             item.layoutManagedHeight = 1;
48746         } else {
48747             item.layoutManagedHeight = 2;
48748         }
48749         this.callParent(arguments);
48750     }
48751 });
48752 /**
48753  * A layout that arranges items vertically down a Container. This layout optionally divides available vertical space
48754  * between child items containing a numeric `flex` configuration.
48755  *
48756  * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
48757  *
48758  *     @example
48759  *     Ext.create('Ext.Panel', {
48760  *         width: 500,
48761  *         height: 400,
48762  *         title: "VBoxLayout Panel",
48763  *         layout: {
48764  *             type: 'vbox',
48765  *             align: 'center'
48766  *         },
48767  *         renderTo: document.body,
48768  *         items: [{
48769  *             xtype: 'panel',
48770  *             title: 'Inner Panel One',
48771  *             width: 250,
48772  *             flex: 2
48773  *         },
48774  *         {
48775  *             xtype: 'panel',
48776  *             title: 'Inner Panel Two',
48777  *             width: 250,
48778  *             flex: 4
48779  *         },
48780  *         {
48781  *             xtype: 'panel',
48782  *             title: 'Inner Panel Three',
48783  *             width: '50%',
48784  *             flex: 4
48785  *         }]
48786  *     });
48787  */
48788 Ext.define('Ext.layout.container.VBox', {
48789
48790     /* Begin Definitions */
48791
48792     alias: ['layout.vbox'],
48793     extend: 'Ext.layout.container.Box',
48794     alternateClassName: 'Ext.layout.VBoxLayout',
48795
48796     /* End Definitions */
48797
48798     /**
48799      * @cfg {String} align
48800      * Controls how the child items of the container are aligned. Acceptable configuration values for this property are:
48801      *
48802      * - **left** : **Default** child items are aligned horizontally at the **left** side of the container
48803      * - **center** : child items are aligned horizontally at the **mid-width** of the container
48804      * - **stretch** : child items are stretched horizontally to fill the width of the container
48805      * - **stretchmax** : child items are stretched horizontally to the size of the largest item.
48806      */
48807     align : 'left', // left, center, stretch, strechmax
48808
48809     //@private
48810     alignCenteringString: 'center',
48811
48812     type: 'vbox',
48813
48814     direction: 'vertical',
48815
48816     // When creating an argument list to setSize, use this order
48817     parallelSizeIndex: 1,
48818     perpendicularSizeIndex: 0,
48819
48820     parallelPrefix: 'height',
48821     parallelPrefixCap: 'Height',
48822     parallelLT: 't',
48823     parallelRB: 'b',
48824     parallelBefore: 'top',
48825     parallelBeforeCap: 'Top',
48826     parallelAfter: 'bottom',
48827     parallelPosition: 'y',
48828
48829     perpendicularPrefix: 'width',
48830     perpendicularPrefixCap: 'Width',
48831     perpendicularLT: 'l',
48832     perpendicularRB: 'r',
48833     perpendicularLeftTop: 'left',
48834     perpendicularRightBottom: 'right',
48835     perpendicularPosition: 'x',
48836     configureItem: function(item) {
48837         if (item.flex) {
48838             item.layoutManagedHeight = 1;
48839         } else {
48840             item.layoutManagedHeight = 2;
48841         }
48842
48843         if (this.align === 'stretch' || this.align === 'stretchmax') {
48844             item.layoutManagedWidth = 1;
48845         } else {
48846             item.layoutManagedWidth = 2;
48847         }
48848         this.callParent(arguments);
48849     }
48850 });
48851 /**
48852  * @class Ext.FocusManager
48853
48854 The FocusManager is responsible for globally:
48855
48856 1. Managing component focus
48857 2. Providing basic keyboard navigation
48858 3. (optional) Provide a visual cue for focused components, in the form of a focus ring/frame.
48859
48860 To activate the FocusManager, simply call `Ext.FocusManager.enable();`. In turn, you may
48861 deactivate the FocusManager by subsequently calling `Ext.FocusManager.disable();.  The
48862 FocusManager is disabled by default.
48863
48864 To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #enable}.
48865
48866 Another feature of the FocusManager is to provide basic keyboard focus navigation scoped to any {@link Ext.container.Container}
48867 that would like to have navigation between its child {@link Ext.Component}'s. The {@link Ext.container.Container} can simply
48868 call {@link #subscribe Ext.FocusManager.subscribe} to take advantage of this feature, and can at any time call
48869 {@link #unsubscribe Ext.FocusManager.unsubscribe} to turn the navigation off.
48870
48871  * @singleton
48872  * @author Jarred Nicholls <jarred@sencha.com>
48873  * @docauthor Jarred Nicholls <jarred@sencha.com>
48874  */
48875 Ext.define('Ext.FocusManager', {
48876     singleton: true,
48877     alternateClassName: 'Ext.FocusMgr',
48878
48879     mixins: {
48880         observable: 'Ext.util.Observable'
48881     },
48882
48883     requires: [
48884         'Ext.ComponentManager',
48885         'Ext.ComponentQuery',
48886         'Ext.util.HashMap',
48887         'Ext.util.KeyNav'
48888     ],
48889
48890     /**
48891      * @property {Boolean} enabled
48892      * Whether or not the FocusManager is currently enabled
48893      */
48894     enabled: false,
48895
48896     /**
48897      * @property {Ext.Component} focusedCmp
48898      * The currently focused component. Defaults to `undefined`.
48899      */
48900
48901     focusElementCls: Ext.baseCSSPrefix + 'focus-element',
48902
48903     focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',
48904
48905     /**
48906      * @property {String[]} whitelist
48907      * A list of xtypes that should ignore certain navigation input keys and
48908      * allow for the default browser event/behavior. These input keys include:
48909      *
48910      * 1. Backspace
48911      * 2. Delete
48912      * 3. Left
48913      * 4. Right
48914      * 5. Up
48915      * 6. Down
48916      *
48917      * The FocusManager will not attempt to navigate when a component is an xtype (or descendents thereof)
48918      * that belongs to this whitelist. E.g., an {@link Ext.form.field.Text} should allow
48919      * the user to move the input cursor left and right, and to delete characters, etc.
48920      */
48921     whitelist: [
48922         'textfield'
48923     ],
48924
48925     tabIndexWhitelist: [
48926         'a',
48927         'button',
48928         'embed',
48929         'frame',
48930         'iframe',
48931         'img',
48932         'input',
48933         'object',
48934         'select',
48935         'textarea'
48936     ],
48937
48938     constructor: function() {
48939         var me = this,
48940             CQ = Ext.ComponentQuery;
48941
48942         me.addEvents(
48943             /**
48944              * @event beforecomponentfocus
48945              * Fires before a component becomes focused. Return `false` to prevent
48946              * the component from gaining focus.
48947              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
48948              * @param {Ext.Component} cmp The component that is being focused
48949              * @param {Ext.Component} previousCmp The component that was previously focused,
48950              * or `undefined` if there was no previously focused component.
48951              */
48952             'beforecomponentfocus',
48953
48954             /**
48955              * @event componentfocus
48956              * Fires after a component becomes focused.
48957              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
48958              * @param {Ext.Component} cmp The component that has been focused
48959              * @param {Ext.Component} previousCmp The component that was previously focused,
48960              * or `undefined` if there was no previously focused component.
48961              */
48962             'componentfocus',
48963
48964             /**
48965              * @event disable
48966              * Fires when the FocusManager is disabled
48967              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
48968              */
48969             'disable',
48970
48971             /**
48972              * @event enable
48973              * Fires when the FocusManager is enabled
48974              * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
48975              */
48976             'enable'
48977         );
48978
48979         // Setup KeyNav that's bound to document to catch all
48980         // unhandled/bubbled key events for navigation
48981         me.keyNav = Ext.create('Ext.util.KeyNav', Ext.getDoc(), {
48982             disabled: true,
48983             scope: me,
48984
48985             backspace: me.focusLast,
48986             enter: me.navigateIn,
48987             esc: me.navigateOut,
48988             tab: me.navigateSiblings
48989
48990             //space: me.navigateIn,
48991             //del: me.focusLast,
48992             //left: me.navigateSiblings,
48993             //right: me.navigateSiblings,
48994             //down: me.navigateSiblings,
48995             //up: me.navigateSiblings
48996         });
48997
48998         me.focusData = {};
48999         me.subscribers = Ext.create('Ext.util.HashMap');
49000         me.focusChain = {};
49001
49002         // Setup some ComponentQuery pseudos
49003         Ext.apply(CQ.pseudos, {
49004             focusable: function(cmps) {
49005                 var len = cmps.length,
49006                     results = [],
49007                     i = 0,
49008                     c,
49009
49010                     isFocusable = function(x) {
49011                         return x && x.focusable !== false && CQ.is(x, '[rendered]:not([destroying]):not([isDestroyed]):not([disabled]){isVisible(true)}{el && c.el.dom && c.el.isVisible()}');
49012                     };
49013
49014                 for (; i < len; i++) {
49015                     c = cmps[i];
49016                     if (isFocusable(c)) {
49017                         results.push(c);
49018                     }
49019                 }
49020
49021                 return results;
49022             },
49023
49024             nextFocus: function(cmps, idx, step) {
49025                 step = step || 1;
49026                 idx = parseInt(idx, 10);
49027
49028                 var len = cmps.length,
49029                     i = idx + step,
49030                     c;
49031
49032                 for (; i != idx; i += step) {
49033                     if (i >= len) {
49034                         i = 0;
49035                     } else if (i < 0) {
49036                         i = len - 1;
49037                     }
49038
49039                     c = cmps[i];
49040                     if (CQ.is(c, ':focusable')) {
49041                         return [c];
49042                     } else if (c.placeholder && CQ.is(c.placeholder, ':focusable')) {
49043                         return [c.placeholder];
49044                     }
49045                 }
49046
49047                 return [];
49048             },
49049
49050             prevFocus: function(cmps, idx) {
49051                 return this.nextFocus(cmps, idx, -1);
49052             },
49053
49054             root: function(cmps) {
49055                 var len = cmps.length,
49056                     results = [],
49057                     i = 0,
49058                     c;
49059
49060                 for (; i < len; i++) {
49061                     c = cmps[i];
49062                     if (!c.ownerCt) {
49063                         results.push(c);
49064                     }
49065                 }
49066
49067                 return results;
49068             }
49069         });
49070     },
49071
49072     /**
49073      * Adds the specified xtype to the {@link #whitelist}.
49074      * @param {String/String[]} xtype Adds the xtype(s) to the {@link #whitelist}.
49075      */
49076     addXTypeToWhitelist: function(xtype) {
49077         var me = this;
49078
49079         if (Ext.isArray(xtype)) {
49080             Ext.Array.forEach(xtype, me.addXTypeToWhitelist, me);
49081             return;
49082         }
49083
49084         if (!Ext.Array.contains(me.whitelist, xtype)) {
49085             me.whitelist.push(xtype);
49086         }
49087     },
49088
49089     clearComponent: function(cmp) {
49090         clearTimeout(this.cmpFocusDelay);
49091         if (!cmp.isDestroyed) {
49092             cmp.blur();
49093         }
49094     },
49095
49096     /**
49097      * Disables the FocusManager by turning of all automatic focus management and keyboard navigation
49098      */
49099     disable: function() {
49100         var me = this;
49101
49102         if (!me.enabled) {
49103             return;
49104         }
49105
49106         delete me.options;
49107         me.enabled = false;
49108
49109         Ext.ComponentManager.all.un('add', me.onComponentCreated, me);
49110
49111         me.removeDOM();
49112
49113         // Stop handling key navigation
49114         me.keyNav.disable();
49115
49116         // disable focus for all components
49117         me.setFocusAll(false);
49118
49119         me.fireEvent('disable', me);
49120     },
49121
49122     /**
49123      * Enables the FocusManager by turning on all automatic focus management and keyboard navigation
49124      * @param {Boolean/Object} options Either `true`/`false` to turn on the focus frame, or an object of the following options:
49125         - focusFrame : Boolean
49126             `true` to show the focus frame around a component when it is focused. Defaults to `false`.
49127      * @markdown
49128      */
49129     enable: function(options) {
49130         var me = this;
49131
49132         if (options === true) {
49133             options = { focusFrame: true };
49134         }
49135         me.options = options = options || {};
49136
49137         if (me.enabled) {
49138             return;
49139         }
49140
49141         // Handle components that are newly added after we are enabled
49142         Ext.ComponentManager.all.on('add', me.onComponentCreated, me);
49143
49144         me.initDOM(options);
49145
49146         // Start handling key navigation
49147         me.keyNav.enable();
49148
49149         // enable focus for all components
49150         me.setFocusAll(true, options);
49151
49152         // Finally, let's focus our global focus el so we start fresh
49153         me.focusEl.focus();
49154         delete me.focusedCmp;
49155
49156         me.enabled = true;
49157         me.fireEvent('enable', me);
49158     },
49159
49160     focusLast: function(e) {
49161         var me = this;
49162
49163         if (me.isWhitelisted(me.focusedCmp)) {
49164             return true;
49165         }
49166
49167         // Go back to last focused item
49168         if (me.previousFocusedCmp) {
49169             me.previousFocusedCmp.focus();
49170         }
49171     },
49172
49173     getRootComponents: function() {
49174         var me = this,
49175             CQ = Ext.ComponentQuery,
49176             inline = CQ.query(':focusable:root:not([floating])'),
49177             floating = CQ.query(':focusable:root[floating]');
49178
49179         // Floating items should go to the top of our root stack, and be ordered
49180         // by their z-index (highest first)
49181         floating.sort(function(a, b) {
49182             return a.el.getZIndex() > b.el.getZIndex();
49183         });
49184
49185         return floating.concat(inline);
49186     },
49187
49188     initDOM: function(options) {
49189         var me = this,
49190             sp = '&#160',
49191             cls = me.focusFrameCls;
49192
49193         if (!Ext.isReady) {
49194             Ext.onReady(me.initDOM, me);
49195             return;
49196         }
49197
49198         // Create global focus element
49199         if (!me.focusEl) {
49200             me.focusEl = Ext.getBody().createChild({
49201                 tabIndex: '-1',
49202                 cls: me.focusElementCls,
49203                 html: sp
49204             });
49205         }
49206
49207         // Create global focus frame
49208         if (!me.focusFrame && options.focusFrame) {
49209             me.focusFrame = Ext.getBody().createChild({
49210                 cls: cls,
49211                 children: [
49212                     { cls: cls + '-top' },
49213                     { cls: cls + '-bottom' },
49214                     { cls: cls + '-left' },
49215                     { cls: cls + '-right' }
49216                 ],
49217                 style: 'top: -100px; left: -100px;'
49218             });
49219             me.focusFrame.setVisibilityMode(Ext.Element.DISPLAY);
49220             me.focusFrameWidth = 2;
49221             me.focusFrame.hide().setLeftTop(0, 0);
49222         }
49223     },
49224
49225     isWhitelisted: function(cmp) {
49226         return cmp && Ext.Array.some(this.whitelist, function(x) {
49227             return cmp.isXType(x);
49228         });
49229     },
49230
49231     navigateIn: function(e) {
49232         var me = this,
49233             focusedCmp = me.focusedCmp,
49234             rootCmps,
49235             firstChild;
49236
49237         if (!focusedCmp) {
49238             // No focus yet, so focus the first root cmp on the page
49239             rootCmps = me.getRootComponents();
49240             if (rootCmps.length) {
49241                 rootCmps[0].focus();
49242             }
49243         } else {
49244             // Drill into child ref items of the focused cmp, if applicable.
49245             // This works for any Component with a getRefItems implementation.
49246             firstChild = Ext.ComponentQuery.query('>:focusable', focusedCmp)[0];
49247             if (firstChild) {
49248                 firstChild.focus();
49249             } else {
49250                 // Let's try to fire a click event, as if it came from the mouse
49251                 if (Ext.isFunction(focusedCmp.onClick)) {
49252                     e.button = 0;
49253                     focusedCmp.onClick(e);
49254                     focusedCmp.focus();
49255                 }
49256             }
49257         }
49258     },
49259
49260     navigateOut: function(e) {
49261         var me = this,
49262             parent;
49263
49264         if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {
49265             me.focusEl.focus();
49266         } else {
49267             parent.focus();
49268         }
49269
49270         // In some browsers (Chrome) FocusManager can handle this before other
49271         // handlers. Ext Windows have their own Esc key handling, so we need to
49272         // return true here to allow the event to bubble.
49273         return true;
49274     },
49275
49276     navigateSiblings: function(e, source, parent) {
49277         var me = this,
49278             src = source || me,
49279             key = e.getKey(),
49280             EO = Ext.EventObject,
49281             goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
49282             checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
49283             nextSelector = goBack ? 'prev' : 'next',
49284             idx, next, focusedCmp;
49285
49286         focusedCmp = (src.focusedCmp && src.focusedCmp.comp) || src.focusedCmp;
49287         if (!focusedCmp && !parent) {
49288             return;
49289         }
49290
49291         if (checkWhitelist && me.isWhitelisted(focusedCmp)) {
49292             return true;
49293         }
49294
49295         parent = parent || focusedCmp.up();
49296         if (parent) {
49297             idx = focusedCmp ? Ext.Array.indexOf(parent.getRefItems(), focusedCmp) : -1;
49298             next = Ext.ComponentQuery.query('>:' + nextSelector + 'Focus(' + idx + ')', parent)[0];
49299             if (next && focusedCmp !== next) {
49300                 next.focus();
49301                 return next;
49302             }
49303         }
49304     },
49305
49306     onComponentBlur: function(cmp, e) {
49307         var me = this;
49308
49309         if (me.focusedCmp === cmp) {
49310             me.previousFocusedCmp = cmp;
49311             delete me.focusedCmp;
49312         }
49313
49314         if (me.focusFrame) {
49315             me.focusFrame.hide();
49316         }
49317     },
49318
49319     onComponentCreated: function(hash, id, cmp) {
49320         this.setFocus(cmp, true, this.options);
49321     },
49322
49323     onComponentDestroy: function(cmp) {
49324         this.setFocus(cmp, false);
49325     },
49326
49327     onComponentFocus: function(cmp, e) {
49328         var me = this,
49329             chain = me.focusChain;
49330
49331         if (!Ext.ComponentQuery.is(cmp, ':focusable')) {
49332             me.clearComponent(cmp);
49333
49334             // Check our focus chain, so we don't run into a never ending recursion
49335             // If we've attempted (unsuccessfully) to focus this component before,
49336             // then we're caught in a loop of child->parent->...->child and we
49337             // need to cut the loop off rather than feed into it.
49338             if (chain[cmp.id]) {
49339                 return;
49340             }
49341
49342             // Try to focus the parent instead
49343             var parent = cmp.up();
49344             if (parent) {
49345                 // Add component to our focus chain to detect infinite focus loop
49346                 // before we fire off an attempt to focus our parent.
49347                 // See the comments above.
49348                 chain[cmp.id] = true;
49349                 parent.focus();
49350             }
49351
49352             return;
49353         }
49354
49355         // Clear our focus chain when we have a focusable component
49356         me.focusChain = {};
49357
49358         // Defer focusing for 90ms so components can do a layout/positioning
49359         // and give us an ability to buffer focuses
49360         clearTimeout(me.cmpFocusDelay);
49361         if (arguments.length !== 2) {
49362             me.cmpFocusDelay = Ext.defer(me.onComponentFocus, 90, me, [cmp, e]);
49363             return;
49364         }
49365
49366         if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
49367             me.clearComponent(cmp);
49368             return;
49369         }
49370
49371         me.focusedCmp = cmp;
49372
49373         // If we have a focus frame, show it around the focused component
49374         if (me.shouldShowFocusFrame(cmp)) {
49375             var cls = '.' + me.focusFrameCls + '-',
49376                 ff = me.focusFrame,
49377                 fw = me.focusFrameWidth,
49378                 box = cmp.el.getPageBox(),
49379
49380             // Size the focus frame's t/b/l/r according to the box
49381             // This leaves a hole in the middle of the frame so user
49382             // interaction w/ the mouse can continue
49383                 bt = box.top,
49384                 bl = box.left,
49385                 bw = box.width,
49386                 bh = box.height,
49387                 ft = ff.child(cls + 'top'),
49388                 fb = ff.child(cls + 'bottom'),
49389                 fl = ff.child(cls + 'left'),
49390                 fr = ff.child(cls + 'right');
49391
49392             ft.setWidth(bw).setLeftTop(bl, bt);
49393             fb.setWidth(bw).setLeftTop(bl, bt + bh - fw);
49394             fl.setHeight(bh - fw - fw).setLeftTop(bl, bt + fw);
49395             fr.setHeight(bh - fw - fw).setLeftTop(bl + bw - fw, bt + fw);
49396
49397             ff.show();
49398         }
49399
49400         me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
49401     },
49402
49403     onComponentHide: function(cmp) {
49404         var me = this,
49405             CQ = Ext.ComponentQuery,
49406             cmpHadFocus = false,
49407             focusedCmp,
49408             parent;
49409
49410         if (me.focusedCmp) {
49411             focusedCmp = CQ.query('[id=' + me.focusedCmp.id + ']', cmp)[0];
49412             cmpHadFocus = me.focusedCmp.id === cmp.id || focusedCmp;
49413
49414             if (focusedCmp) {
49415                 me.clearComponent(focusedCmp);
49416             }
49417         }
49418
49419         me.clearComponent(cmp);
49420
49421         if (cmpHadFocus) {
49422             parent = CQ.query('^:focusable', cmp)[0];
49423             if (parent) {
49424                 parent.focus();
49425             }
49426         }
49427     },
49428
49429     removeDOM: function() {
49430         var me = this;
49431
49432         // If we are still enabled globally, or there are still subscribers
49433         // then we will halt here, since our DOM stuff is still being used
49434         if (me.enabled || me.subscribers.length) {
49435             return;
49436         }
49437
49438         Ext.destroy(
49439             me.focusEl,
49440             me.focusFrame
49441         );
49442         delete me.focusEl;
49443         delete me.focusFrame;
49444         delete me.focusFrameWidth;
49445     },
49446
49447     /**
49448      * Removes the specified xtype from the {@link #whitelist}.
49449      * @param {String/String[]} xtype Removes the xtype(s) from the {@link #whitelist}.
49450      */
49451     removeXTypeFromWhitelist: function(xtype) {
49452         var me = this;
49453
49454         if (Ext.isArray(xtype)) {
49455             Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
49456             return;
49457         }
49458
49459         Ext.Array.remove(me.whitelist, xtype);
49460     },
49461
49462     setFocus: function(cmp, focusable, options) {
49463         var me = this,
49464             el, dom, data,
49465
49466             needsTabIndex = function(n) {
49467                 return !Ext.Array.contains(me.tabIndexWhitelist, n.tagName.toLowerCase())
49468                     && n.tabIndex <= 0;
49469             };
49470
49471         options = options || {};
49472
49473         // Come back and do this after the component is rendered
49474         if (!cmp.rendered) {
49475             cmp.on('afterrender', Ext.pass(me.setFocus, arguments, me), me, { single: true });
49476             return;
49477         }
49478
49479         el = cmp.getFocusEl();
49480         dom = el.dom;
49481
49482         // Decorate the component's focus el for focus-ability
49483         if ((focusable && !me.focusData[cmp.id]) || (!focusable && me.focusData[cmp.id])) {
49484             if (focusable) {
49485                 data = {
49486                     focusFrame: options.focusFrame
49487                 };
49488
49489                 // Only set -1 tabIndex if we need it
49490                 // inputs, buttons, and anchor tags do not need it,
49491                 // and neither does any DOM that has it set already
49492                 // programmatically or in markup.
49493                 if (needsTabIndex(dom)) {
49494                     data.tabIndex = dom.tabIndex;
49495                     dom.tabIndex = -1;
49496                 }
49497
49498                 el.on({
49499                     focus: data.focusFn = Ext.bind(me.onComponentFocus, me, [cmp], 0),
49500                     blur: data.blurFn = Ext.bind(me.onComponentBlur, me, [cmp], 0),
49501                     scope: me
49502                 });
49503                 cmp.on({
49504                     hide: me.onComponentHide,
49505                     close: me.onComponentHide,
49506                     beforedestroy: me.onComponentDestroy,
49507                     scope: me
49508                 });
49509
49510                 me.focusData[cmp.id] = data;
49511             } else {
49512                 data = me.focusData[cmp.id];
49513                 if ('tabIndex' in data) {
49514                     dom.tabIndex = data.tabIndex;
49515                 }
49516                 el.un('focus', data.focusFn, me);
49517                 el.un('blur', data.blurFn, me);
49518                 cmp.un('hide', me.onComponentHide, me);
49519                 cmp.un('close', me.onComponentHide, me);
49520                 cmp.un('beforedestroy', me.onComponentDestroy, me);
49521
49522                 delete me.focusData[cmp.id];
49523             }
49524         }
49525     },
49526
49527     setFocusAll: function(focusable, options) {
49528         var me = this,
49529             cmps = Ext.ComponentManager.all.getArray(),
49530             len = cmps.length,
49531             cmp,
49532             i = 0;
49533
49534         for (; i < len; i++) {
49535             me.setFocus(cmps[i], focusable, options);
49536         }
49537     },
49538
49539     setupSubscriberKeys: function(container, keys) {
49540         var me = this,
49541             el = container.getFocusEl(),
49542             scope = keys.scope,
49543             handlers = {
49544                 backspace: me.focusLast,
49545                 enter: me.navigateIn,
49546                 esc: me.navigateOut,
49547                 scope: me
49548             },
49549
49550             navSiblings = function(e) {
49551                 if (me.focusedCmp === container) {
49552                     // Root the sibling navigation to this container, so that we
49553                     // can automatically dive into the container, rather than forcing
49554                     // the user to hit the enter key to dive in.
49555                     return me.navigateSiblings(e, me, container);
49556                 } else {
49557                     return me.navigateSiblings(e);
49558                 }
49559             };
49560
49561         Ext.iterate(keys, function(key, cb) {
49562             handlers[key] = function(e) {
49563                 var ret = navSiblings(e);
49564
49565                 if (Ext.isFunction(cb) && cb.call(scope || container, e, ret) === true) {
49566                     return true;
49567                 }
49568
49569                 return ret;
49570             };
49571         }, me);
49572
49573         return Ext.create('Ext.util.KeyNav', el, handlers);
49574     },
49575
49576     shouldShowFocusFrame: function(cmp) {
49577         var me = this,
49578             opts = me.options || {};
49579
49580         if (!me.focusFrame || !cmp) {
49581             return false;
49582         }
49583
49584         // Global trumps
49585         if (opts.focusFrame) {
49586             return true;
49587         }
49588
49589         if (me.focusData[cmp.id].focusFrame) {
49590             return true;
49591         }
49592
49593         return false;
49594     },
49595
49596     /**
49597      * Subscribes an {@link Ext.container.Container} to provide basic keyboard focus navigation between its child {@link Ext.Component}'s.
49598      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} on which to enable keyboard functionality and focus management.
49599      * @param {Boolean/Object} options An object of the following options
49600      * @param {Array/Object} options.keys
49601      * An array containing the string names of navigation keys to be supported. The allowed values are:
49602      *
49603      *   - 'left'
49604      *   - 'right'
49605      *   - 'up'
49606      *   - 'down'
49607      *
49608      * 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.:
49609      *
49610      *     {
49611      *         left: this.onLeftKey,
49612      *         right: this.onRightKey,
49613      *         scope: this
49614      *     }
49615      *
49616      * @param {Boolean} options.focusFrame
49617      * `true` to show the focus frame around a component when it is focused. Defaults to `false`.
49618      */
49619     subscribe: function(container, options) {
49620         var me = this,
49621             EA = Ext.Array,
49622             data = {},
49623             subs = me.subscribers,
49624
49625             // Recursively add focus ability as long as a descendent container isn't
49626             // itself subscribed to the FocusManager, or else we'd have unwanted side
49627             // effects for subscribing a descendent container twice.
49628             safeSetFocus = function(cmp) {
49629                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
49630                     EA.forEach(cmp.query('>'), safeSetFocus);
49631                     me.setFocus(cmp, true, options);
49632                     cmp.on('add', data.onAdd, me);
49633                 } else if (!cmp.isContainer) {
49634                     me.setFocus(cmp, true, options);
49635                 }
49636             };
49637
49638         // We only accept containers
49639         if (!container || !container.isContainer) {
49640             return;
49641         }
49642
49643         if (!container.rendered) {
49644             container.on('afterrender', Ext.pass(me.subscribe, arguments, me), me, { single: true });
49645             return;
49646         }
49647
49648         // Init the DOM, incase this is the first time it will be used
49649         me.initDOM(options);
49650
49651         // Create key navigation for subscriber based on keys option
49652         data.keyNav = me.setupSubscriberKeys(container, options.keys);
49653
49654         // We need to keep track of components being added to our subscriber
49655         // and any containers nested deeply within it (omg), so let's do that.
49656         // Components that are removed are globally handled.
49657         // Also keep track of destruction of our container for auto-unsubscribe.
49658         data.onAdd = function(ct, cmp, idx) {
49659             safeSetFocus(cmp);
49660         };
49661         container.on('beforedestroy', me.unsubscribe, me);
49662
49663         // Now we setup focusing abilities for the container and all its components
49664         safeSetFocus(container);
49665
49666         // Add to our subscribers list
49667         subs.add(container.id, data);
49668     },
49669
49670     /**
49671      * Unsubscribes an {@link Ext.container.Container} from keyboard focus management.
49672      * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} to unsubscribe from the FocusManager.
49673      */
49674     unsubscribe: function(container) {
49675         var me = this,
49676             EA = Ext.Array,
49677             subs = me.subscribers,
49678             data,
49679
49680             // Recursively remove focus ability as long as a descendent container isn't
49681             // itself subscribed to the FocusManager, or else we'd have unwanted side
49682             // effects for unsubscribing an ancestor container.
49683             safeSetFocus = function(cmp) {
49684                 if (cmp.isContainer && !subs.containsKey(cmp.id)) {
49685                     EA.forEach(cmp.query('>'), safeSetFocus);
49686                     me.setFocus(cmp, false);
49687                     cmp.un('add', data.onAdd, me);
49688                 } else if (!cmp.isContainer) {
49689                     me.setFocus(cmp, false);
49690                 }
49691             };
49692
49693         if (!container || !subs.containsKey(container.id)) {
49694             return;
49695         }
49696
49697         data = subs.get(container.id);
49698         data.keyNav.destroy();
49699         container.un('beforedestroy', me.unsubscribe, me);
49700         subs.removeAtKey(container.id);
49701         safeSetFocus(container);
49702         me.removeDOM();
49703     }
49704 });
49705 /**
49706  * Basic Toolbar class. Although the {@link Ext.container.Container#defaultType defaultType} for Toolbar is {@link Ext.button.Button button}, Toolbar
49707  * elements (child items for the Toolbar container) may be virtually any type of Component. Toolbar elements can be created explicitly via their
49708  * constructors, or implicitly via their xtypes, and can be {@link #add}ed dynamically.
49709  *
49710  * ## Some items have shortcut strings for creation:
49711  *
49712  * | Shortcut | xtype         | Class                         | Description
49713  * |:---------|:--------------|:------------------------------|:---------------------------------------------------
49714  * | `->`     | `tbfill`      | {@link Ext.toolbar.Fill}      | begin using the right-justified button container
49715  * | `-`      | `tbseparator` | {@link Ext.toolbar.Separator} | add a vertical separator bar between toolbar items
49716  * | ` `      | `tbspacer`    | {@link Ext.toolbar.Spacer}    | add horiztonal space between elements
49717  *
49718  *     @example
49719  *     Ext.create('Ext.toolbar.Toolbar', {
49720  *         renderTo: document.body,
49721  *         width   : 500,
49722  *         items: [
49723  *             {
49724  *                 // xtype: 'button', // default for Toolbars
49725  *                 text: 'Button'
49726  *             },
49727  *             {
49728  *                 xtype: 'splitbutton',
49729  *                 text : 'Split Button'
49730  *             },
49731  *             // begin using the right-justified button container
49732  *             '->', // same as { xtype: 'tbfill' }
49733  *             {
49734  *                 xtype    : 'textfield',
49735  *                 name     : 'field1',
49736  *                 emptyText: 'enter search term'
49737  *             },
49738  *             // add a vertical separator bar between toolbar items
49739  *             '-', // same as {xtype: 'tbseparator'} to create Ext.toolbar.Separator
49740  *             'text 1', // same as {xtype: 'tbtext', text: 'text1'} to create Ext.toolbar.TextItem
49741  *             { xtype: 'tbspacer' },// same as ' ' to create Ext.toolbar.Spacer
49742  *             'text 2',
49743  *             { xtype: 'tbspacer', width: 50 }, // add a 50px space
49744  *             'text 3'
49745  *         ]
49746  *     });
49747  *
49748  * Toolbars have {@link #enable} and {@link #disable} methods which when called, will enable/disable all items within your toolbar.
49749  *
49750  *     @example
49751  *     Ext.create('Ext.toolbar.Toolbar', {
49752  *         renderTo: document.body,
49753  *         width   : 400,
49754  *         items: [
49755  *             {
49756  *                 text: 'Button'
49757  *             },
49758  *             {
49759  *                 xtype: 'splitbutton',
49760  *                 text : 'Split Button'
49761  *             },
49762  *             '->',
49763  *             {
49764  *                 xtype    : 'textfield',
49765  *                 name     : 'field1',
49766  *                 emptyText: 'enter search term'
49767  *             }
49768  *         ]
49769  *     });
49770  *
49771  * Example
49772  *
49773  *     @example
49774  *     var enableBtn = Ext.create('Ext.button.Button', {
49775  *         text    : 'Enable All Items',
49776  *         disabled: true,
49777  *         scope   : this,
49778  *         handler : function() {
49779  *             //disable the enable button and enable the disable button
49780  *             enableBtn.disable();
49781  *             disableBtn.enable();
49782  *
49783  *             //enable the toolbar
49784  *             toolbar.enable();
49785  *         }
49786  *     });
49787  *
49788  *     var disableBtn = Ext.create('Ext.button.Button', {
49789  *         text    : 'Disable All Items',
49790  *         scope   : this,
49791  *         handler : function() {
49792  *             //enable the enable button and disable button
49793  *             disableBtn.disable();
49794  *             enableBtn.enable();
49795  *
49796  *             //disable the toolbar
49797  *             toolbar.disable();
49798  *         }
49799  *     });
49800  *
49801  *     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
49802  *         renderTo: document.body,
49803  *         width   : 400,
49804  *         margin  : '5 0 0 0',
49805  *         items   : [enableBtn, disableBtn]
49806  *     });
49807  *
49808  * 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
49809  * which remove all items within the toolbar.
49810  *
49811  *     @example
49812  *     var toolbar = Ext.create('Ext.toolbar.Toolbar', {
49813  *         renderTo: document.body,
49814  *         width   : 700,
49815  *         items: [
49816  *             {
49817  *                 text: 'Example Button'
49818  *             }
49819  *         ]
49820  *     });
49821  *
49822  *     var addedItems = [];
49823  *
49824  *     Ext.create('Ext.toolbar.Toolbar', {
49825  *         renderTo: document.body,
49826  *         width   : 700,
49827  *         margin  : '5 0 0 0',
49828  *         items   : [
49829  *             {
49830  *                 text   : 'Add a button',
49831  *                 scope  : this,
49832  *                 handler: function() {
49833  *                     var text = prompt('Please enter the text for your button:');
49834  *                     addedItems.push(toolbar.add({
49835  *                         text: text
49836  *                     }));
49837  *                 }
49838  *             },
49839  *             {
49840  *                 text   : 'Add a text item',
49841  *                 scope  : this,
49842  *                 handler: function() {
49843  *                     var text = prompt('Please enter the text for your item:');
49844  *                     addedItems.push(toolbar.add(text));
49845  *                 }
49846  *             },
49847  *             {
49848  *                 text   : 'Add a toolbar seperator',
49849  *                 scope  : this,
49850  *                 handler: function() {
49851  *                     addedItems.push(toolbar.add('-'));
49852  *                 }
49853  *             },
49854  *             {
49855  *                 text   : 'Add a toolbar spacer',
49856  *                 scope  : this,
49857  *                 handler: function() {
49858  *                     addedItems.push(toolbar.add('->'));
49859  *                 }
49860  *             },
49861  *             '->',
49862  *             {
49863  *                 text   : 'Remove last inserted item',
49864  *                 scope  : this,
49865  *                 handler: function() {
49866  *                     if (addedItems.length) {
49867  *                         toolbar.remove(addedItems.pop());
49868  *                     } else if (toolbar.items.length) {
49869  *                         toolbar.remove(toolbar.items.last());
49870  *                     } else {
49871  *                         alert('No items in the toolbar');
49872  *                     }
49873  *                 }
49874  *             },
49875  *             {
49876  *                 text   : 'Remove all items',
49877  *                 scope  : this,
49878  *                 handler: function() {
49879  *                     toolbar.removeAll();
49880  *                 }
49881  *             }
49882  *         ]
49883  *     });
49884  *
49885  * @constructor
49886  * Creates a new Toolbar
49887  * @param {Object/Object[]} config A config object or an array of buttons to <code>{@link #add}</code>
49888  * @docauthor Robert Dougan <rob@sencha.com>
49889  */
49890 Ext.define('Ext.toolbar.Toolbar', {
49891     extend: 'Ext.container.Container',
49892     requires: [
49893         'Ext.toolbar.Fill',
49894         'Ext.layout.container.HBox',
49895         'Ext.layout.container.VBox',
49896         'Ext.FocusManager'
49897     ],
49898     uses: [
49899         'Ext.toolbar.Separator'
49900     ],
49901     alias: 'widget.toolbar',
49902     alternateClassName: 'Ext.Toolbar',
49903
49904     isToolbar: true,
49905     baseCls  : Ext.baseCSSPrefix + 'toolbar',
49906     ariaRole : 'toolbar',
49907
49908     defaultType: 'button',
49909
49910     /**
49911      * @cfg {Boolean} vertical
49912      * Set to `true` to make the toolbar vertical. The layout will become a `vbox`.
49913      */
49914     vertical: false,
49915
49916     /**
49917      * @cfg {String/Object} layout
49918      * This class assigns a default layout (`layout: 'hbox'`).
49919      * Developers _may_ override this configuration option if another layout
49920      * is required (the constructor must be passed a configuration object in this
49921      * case instead of an array).
49922      * See {@link Ext.container.Container#layout} for additional information.
49923      */
49924
49925     /**
49926      * @cfg {Boolean} enableOverflow
49927      * Configure true to make the toolbar provide a button which activates a dropdown Menu to show
49928      * items which overflow the Toolbar's width.
49929      */
49930     enableOverflow: false,
49931
49932     /**
49933      * @cfg {String} menuTriggerCls
49934      * Configure the icon class of the overflow button.
49935      */
49936     menuTriggerCls: Ext.baseCSSPrefix + 'toolbar-more-icon',
49937     
49938     // private
49939     trackMenus: true,
49940
49941     itemCls: Ext.baseCSSPrefix + 'toolbar-item',
49942
49943     initComponent: function() {
49944         var me = this,
49945             keys;
49946
49947         // check for simplified (old-style) overflow config:
49948         if (!me.layout && me.enableOverflow) {
49949             me.layout = { overflowHandler: 'Menu' };
49950         }
49951
49952         if (me.dock === 'right' || me.dock === 'left') {
49953             me.vertical = true;
49954         }
49955
49956         me.layout = Ext.applyIf(Ext.isString(me.layout) ? {
49957             type: me.layout
49958         } : me.layout || {}, {
49959             type: me.vertical ? 'vbox' : 'hbox',
49960             align: me.vertical ? 'stretchmax' : 'middle',
49961             clearInnerCtOnLayout: true
49962         });
49963
49964         if (me.vertical) {
49965             me.addClsWithUI('vertical');
49966         }
49967
49968         // @TODO: remove this hack and implement a more general solution
49969         if (me.ui === 'footer') {
49970             me.ignoreBorderManagement = true;
49971         }
49972
49973         me.callParent();
49974
49975         /**
49976          * @event overflowchange
49977          * Fires after the overflow state has changed.
49978          * @param {Object} c The Container
49979          * @param {Boolean} lastOverflow overflow state
49980          */
49981         me.addEvents('overflowchange');
49982
49983         // Subscribe to Ext.FocusManager for key navigation
49984         keys = me.vertical ? ['up', 'down'] : ['left', 'right'];
49985         Ext.FocusManager.subscribe(me, {
49986             keys: keys
49987         });
49988     },
49989
49990     getRefItems: function(deep) {
49991         var me = this,
49992             items = me.callParent(arguments),
49993             layout = me.layout,
49994             handler;
49995
49996         if (deep && me.enableOverflow) {
49997             handler = layout.overflowHandler;
49998             if (handler && handler.menu) {
49999                 items = items.concat(handler.menu.getRefItems(deep));
50000             }
50001         }
50002         return items;
50003     },
50004
50005     /**
50006      * Adds element(s) to the toolbar -- this function takes a variable number of
50007      * arguments of mixed type and adds them to the toolbar.
50008      *
50009      * **Note**: See the notes within {@link Ext.container.Container#add}.
50010      *
50011      * @param {Object...} args The following types of arguments are all valid:
50012      *  - `{@link Ext.button.Button config}`: A valid button config object
50013      *  - `HtmlElement`: Any standard HTML element
50014      *  - `Field`: Any form field
50015      *  - `Item`: Any subclass of {@link Ext.toolbar.Item}
50016      *  - `String`: Any generic string (gets wrapped in a {@link Ext.toolbar.TextItem}).
50017      *  Note that there are a few special strings that are treated differently as explained next.
50018      *  - `'-'`: Creates a separator element
50019      *  - `' '`: Creates a spacer element
50020      *  - `'->'`: Creates a fill element
50021      *
50022      * @method add
50023      */
50024
50025     // private
50026     lookupComponent: function(c) {
50027         if (Ext.isString(c)) {
50028             var shortcut = Ext.toolbar.Toolbar.shortcuts[c];
50029             if (shortcut) {
50030                 c = {
50031                     xtype: shortcut
50032                 };
50033             } else {
50034                 c = {
50035                     xtype: 'tbtext',
50036                     text: c
50037                 };
50038             }
50039             this.applyDefaults(c);
50040         }
50041         return this.callParent(arguments);
50042     },
50043
50044     // private
50045     applyDefaults: function(c) {
50046         if (!Ext.isString(c)) {
50047             c = this.callParent(arguments);
50048             var d = this.internalDefaults;
50049             if (c.events) {
50050                 Ext.applyIf(c.initialConfig, d);
50051                 Ext.apply(c, d);
50052             } else {
50053                 Ext.applyIf(c, d);
50054             }
50055         }
50056         return c;
50057     },
50058
50059     // private
50060     trackMenu: function(item, remove) {
50061         if (this.trackMenus && item.menu) {
50062             var method = remove ? 'mun' : 'mon',
50063                 me = this;
50064
50065             me[method](item, 'mouseover', me.onButtonOver, me);
50066             me[method](item, 'menushow', me.onButtonMenuShow, me);
50067             me[method](item, 'menuhide', me.onButtonMenuHide, me);
50068         }
50069     },
50070
50071     // private
50072     constructButton: function(item) {
50073         return item.events ? item : this.createComponent(item, item.split ? 'splitbutton' : this.defaultType);
50074     },
50075
50076     // private
50077     onBeforeAdd: function(component) {
50078         if (component.is('field') || (component.is('button') && this.ui != 'footer')) {
50079             component.ui = component.ui + '-toolbar';
50080         }
50081
50082         // Any separators needs to know if is vertical or not
50083         if (component instanceof Ext.toolbar.Separator) {
50084             component.setUI((this.vertical) ? 'vertical' : 'horizontal');
50085         }
50086
50087         this.callParent(arguments);
50088     },
50089
50090     // private
50091     onAdd: function(component) {
50092         this.callParent(arguments);
50093
50094         this.trackMenu(component);
50095         if (this.disabled) {
50096             component.disable();
50097         }
50098     },
50099
50100     // private
50101     onRemove: function(c) {
50102         this.callParent(arguments);
50103         this.trackMenu(c, true);
50104     },
50105
50106     // private
50107     onButtonOver: function(btn){
50108         if (this.activeMenuBtn && this.activeMenuBtn != btn) {
50109             this.activeMenuBtn.hideMenu();
50110             btn.showMenu();
50111             this.activeMenuBtn = btn;
50112         }
50113     },
50114
50115     // private
50116     onButtonMenuShow: function(btn) {
50117         this.activeMenuBtn = btn;
50118     },
50119
50120     // private
50121     onButtonMenuHide: function(btn) {
50122         delete this.activeMenuBtn;
50123     }
50124 }, function() {
50125     this.shortcuts = {
50126         '-' : 'tbseparator',
50127         ' ' : 'tbspacer',
50128         '->': 'tbfill'
50129     };
50130 });
50131 /**
50132  * @class Ext.panel.AbstractPanel
50133  * @extends Ext.container.Container
50134  * A base class which provides methods common to Panel classes across the Sencha product range.
50135  * @private
50136  */
50137 Ext.define('Ext.panel.AbstractPanel', {
50138
50139     /* Begin Definitions */
50140
50141     extend: 'Ext.container.Container',
50142
50143     requires: ['Ext.util.MixedCollection', 'Ext.Element', 'Ext.toolbar.Toolbar'],
50144
50145     /* End Definitions */
50146
50147     /**
50148      * @cfg {String} [baseCls='x-panel']
50149      * The base CSS class to apply to this panel's element.
50150      */
50151     baseCls : Ext.baseCSSPrefix + 'panel',
50152
50153     /**
50154      * @cfg {Number/String} bodyPadding
50155      * A shortcut for setting a padding style on the body element. The value can either be
50156      * a number to be applied to all sides, or a normal css string describing padding.
50157      */
50158
50159     /**
50160      * @cfg {Boolean} bodyBorder
50161      * A shortcut to add or remove the border on the body of a panel. This only applies to a panel
50162      * which has the {@link #frame} configuration set to `true`.
50163      */
50164
50165     /**
50166      * @cfg {String/Object/Function} bodyStyle
50167      * Custom CSS styles to be applied to the panel's body element, which can be supplied as a valid CSS style string,
50168      * an object containing style property name/value pairs or a function that returns such a string or object.
50169      * For example, these two formats are interpreted to be equivalent:<pre><code>
50170 bodyStyle: 'background:#ffc; padding:10px;'
50171
50172 bodyStyle: {
50173     background: '#ffc',
50174     padding: '10px'
50175 }
50176      * </code></pre>
50177      */
50178
50179     /**
50180      * @cfg {String/String[]} bodyCls
50181      * A CSS class, space-delimited string of classes, or array of classes to be applied to the panel's body element.
50182      * The following examples are all valid:<pre><code>
50183 bodyCls: 'foo'
50184 bodyCls: 'foo bar'
50185 bodyCls: ['foo', 'bar']
50186      * </code></pre>
50187      */
50188
50189     isPanel: true,
50190
50191     componentLayout: 'dock',
50192
50193     /**
50194      * @cfg {Object} defaultDockWeights
50195      * This object holds the default weights applied to dockedItems that have no weight. These start with a
50196      * weight of 1, to allow negative weights to insert before top items and are odd numbers
50197      * so that even weights can be used to get between different dock orders.
50198      *
50199      * To make default docking order match border layout, do this:
50200      * <pre><code>
50201 Ext.panel.AbstractPanel.prototype.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };</code></pre>
50202      * Changing these defaults as above or individually on this object will effect all Panels.
50203      * To change the defaults on a single panel, you should replace the entire object:
50204      * <pre><code>
50205 initComponent: function () {
50206     // NOTE: Don't change members of defaultDockWeights since the object is shared.
50207     this.defaultDockWeights = { top: 1, bottom: 3, left: 5, right: 7 };
50208
50209     this.callParent();
50210 }</code></pre>
50211      *
50212      * To change only one of the default values, you do this:
50213      * <pre><code>
50214 initComponent: function () {
50215     // NOTE: Don't change members of defaultDockWeights since the object is shared.
50216     this.defaultDockWeights = Ext.applyIf({ top: 10 }, this.defaultDockWeights);
50217
50218     this.callParent();
50219 }</code></pre>
50220      */
50221     defaultDockWeights: { top: 1, left: 3, right: 5, bottom: 7 },
50222
50223     renderTpl: [
50224         '<div id="{id}-body" class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl>',
50225             ' {baseCls}-body-{ui}<tpl if="uiCls">',
50226                 '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>',
50227             '</tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>>',
50228         '</div>'
50229     ],
50230
50231     // TODO: Move code examples into product-specific files. The code snippet below is Touch only.
50232     /**
50233      * @cfg {Object/Object[]} dockedItems
50234      * A component or series of components to be added as docked items to this panel.
50235      * The docked items can be docked to either the top, right, left or bottom of a panel.
50236      * This is typically used for things like toolbars or tab bars:
50237      * <pre><code>
50238 var panel = new Ext.panel.Panel({
50239     fullscreen: true,
50240     dockedItems: [{
50241         xtype: 'toolbar',
50242         dock: 'top',
50243         items: [{
50244             text: 'Docked to the top'
50245         }]
50246     }]
50247 });</code></pre>
50248      */
50249
50250     border: true,
50251
50252     initComponent : function() {
50253         var me = this;
50254
50255         me.addEvents(
50256             /**
50257              * @event bodyresize
50258              * Fires after the Panel has been resized.
50259              * @param {Ext.panel.Panel} p the Panel which has been resized.
50260              * @param {Number} width The Panel body's new width.
50261              * @param {Number} height The Panel body's new height.
50262              */
50263             'bodyresize'
50264             // // inherited
50265             // 'activate',
50266             // // inherited
50267             // 'deactivate'
50268         );
50269
50270         me.addChildEls('body');
50271
50272         //!frame
50273         //!border
50274
50275         if (me.frame && me.border && me.bodyBorder === undefined) {
50276             me.bodyBorder = false;
50277         }
50278         if (me.frame && me.border && (me.bodyBorder === false || me.bodyBorder === 0)) {
50279             me.manageBodyBorders = true;
50280         }
50281
50282         me.callParent();
50283     },
50284
50285     // @private
50286     initItems : function() {
50287         var me = this,
50288             items = me.dockedItems;
50289
50290         me.callParent();
50291         me.dockedItems = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
50292         if (items) {
50293             me.addDocked(items);
50294         }
50295     },
50296
50297     /**
50298      * Finds a docked component by id, itemId or position. Also see {@link #getDockedItems}
50299      * @param {String/Number} comp The id, itemId or position of the docked component (see {@link #getComponent} for details)
50300      * @return {Ext.Component} The docked component (if found)
50301      */
50302     getDockedComponent: function(comp) {
50303         if (Ext.isObject(comp)) {
50304             comp = comp.getItemId();
50305         }
50306         return this.dockedItems.get(comp);
50307     },
50308
50309     /**
50310      * Attempts a default component lookup (see {@link Ext.container.Container#getComponent}). If the component is not found in the normal
50311      * items, the dockedItems are searched and the matched component (if any) returned (see {@link #getDockedComponent}). Note that docked
50312      * items will only be matched by component id or itemId -- if you pass a numeric index only non-docked child components will be searched.
50313      * @param {String/Number} comp The component id, itemId or position to find
50314      * @return {Ext.Component} The component (if found)
50315      */
50316     getComponent: function(comp) {
50317         var component = this.callParent(arguments);
50318         if (component === undefined && !Ext.isNumber(comp)) {
50319             // If the arg is a numeric index skip docked items
50320             component = this.getDockedComponent(comp);
50321         }
50322         return component;
50323     },
50324
50325     /**
50326      * Parses the {@link bodyStyle} config if available to create a style string that will be applied to the body element.
50327      * This also includes {@link bodyPadding} and {@link bodyBorder} if available.
50328      * @return {String} A CSS style string with body styles, padding and border.
50329      * @private
50330      */
50331     initBodyStyles: function() {
50332         var me = this,
50333             bodyStyle = me.bodyStyle,
50334             styles = [],
50335             Element = Ext.Element,
50336             prop;
50337
50338         if (Ext.isFunction(bodyStyle)) {
50339             bodyStyle = bodyStyle();
50340         }
50341         if (Ext.isString(bodyStyle)) {
50342             styles = bodyStyle.split(';');
50343         } else {
50344             for (prop in bodyStyle) {
50345                 if (bodyStyle.hasOwnProperty(prop)) {
50346                     styles.push(prop + ':' + bodyStyle[prop]);
50347                 }
50348             }
50349         }
50350
50351         if (me.bodyPadding !== undefined) {
50352             styles.push('padding: ' + Element.unitizeBox((me.bodyPadding === true) ? 5 : me.bodyPadding));
50353         }
50354         if (me.frame && me.bodyBorder) {
50355             if (!Ext.isNumber(me.bodyBorder)) {
50356                 me.bodyBorder = 1;
50357             }
50358             styles.push('border-width: ' + Element.unitizeBox(me.bodyBorder));
50359         }
50360         delete me.bodyStyle;
50361         return styles.length ? styles.join(';') : undefined;
50362     },
50363
50364     /**
50365      * Parse the {@link bodyCls} config if available to create a comma-delimited string of
50366      * CSS classes to be applied to the body element.
50367      * @return {String} The CSS class(es)
50368      * @private
50369      */
50370     initBodyCls: function() {
50371         var me = this,
50372             cls = '',
50373             bodyCls = me.bodyCls;
50374
50375         if (bodyCls) {
50376             Ext.each(bodyCls, function(v) {
50377                 cls += " " + v;
50378             });
50379             delete me.bodyCls;
50380         }
50381         return cls.length > 0 ? cls : undefined;
50382     },
50383
50384     /**
50385      * Initialized the renderData to be used when rendering the renderTpl.
50386      * @return {Object} Object with keys and values that are going to be applied to the renderTpl
50387      * @private
50388      */
50389     initRenderData: function() {
50390         return Ext.applyIf(this.callParent(), {
50391             bodyStyle: this.initBodyStyles(),
50392             bodyCls: this.initBodyCls()
50393         });
50394     },
50395
50396     /**
50397      * Adds docked item(s) to the panel.
50398      * @param {Object/Object[]} component The Component or array of components to add. The components
50399      * must include a 'dock' parameter on each component to indicate where it should be docked ('top', 'right',
50400      * 'bottom', 'left').
50401      * @param {Number} pos (optional) The index at which the Component will be added
50402      */
50403     addDocked : function(items, pos) {
50404         var me = this,
50405             i = 0,
50406             item, length;
50407
50408         items = me.prepareItems(items);
50409         length = items.length;
50410
50411         for (; i < length; i++) {
50412             item = items[i];
50413             item.dock = item.dock || 'top';
50414
50415             // Allow older browsers to target docked items to style without borders
50416             if (me.border === false) {
50417                 // item.cls = item.cls || '' + ' ' + me.baseCls + '-noborder-docked-' + item.dock;
50418             }
50419
50420             if (pos !== undefined) {
50421                 me.dockedItems.insert(pos + i, item);
50422             }
50423             else {
50424                 me.dockedItems.add(item);
50425             }
50426             item.onAdded(me, i);
50427             me.onDockedAdd(item);
50428         }
50429
50430         // Set flag which means that beforeLayout will not veto the layout due to the size not changing
50431         me.componentLayout.childrenChanged = true;
50432         if (me.rendered && !me.suspendLayout) {
50433             me.doComponentLayout();
50434         }
50435         return items;
50436     },
50437
50438     // Placeholder empty functions
50439     onDockedAdd : Ext.emptyFn,
50440     onDockedRemove : Ext.emptyFn,
50441
50442     /**
50443      * Inserts docked item(s) to the panel at the indicated position.
50444      * @param {Number} pos The index at which the Component will be inserted
50445      * @param {Object/Object[]} component. The Component or array of components to add. The components
50446      * must include a 'dock' paramater on each component to indicate where it should be docked ('top', 'right',
50447      * 'bottom', 'left').
50448      */
50449     insertDocked : function(pos, items) {
50450         this.addDocked(items, pos);
50451     },
50452
50453     /**
50454      * Removes the docked item from the panel.
50455      * @param {Ext.Component} item. The Component to remove.
50456      * @param {Boolean} autoDestroy (optional) Destroy the component after removal.
50457      */
50458     removeDocked : function(item, autoDestroy) {
50459         var me = this,
50460             layout,
50461             hasLayout;
50462
50463         if (!me.dockedItems.contains(item)) {
50464             return item;
50465         }
50466
50467         layout = me.componentLayout;
50468         hasLayout = layout && me.rendered;
50469
50470         if (hasLayout) {
50471             layout.onRemove(item);
50472         }
50473
50474         me.dockedItems.remove(item);
50475         item.onRemoved();
50476         me.onDockedRemove(item);
50477
50478         if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
50479             item.destroy();
50480         } else if (hasLayout) {
50481             // not destroying, make any layout related removals
50482             layout.afterRemove(item);    
50483         }
50484
50485
50486         // Set flag which means that beforeLayout will not veto the layout due to the size not changing
50487         me.componentLayout.childrenChanged = true;
50488         if (!me.destroying && !me.suspendLayout) {
50489             me.doComponentLayout();
50490         }
50491
50492         return item;
50493     },
50494
50495     /**
50496      * Retrieve an array of all currently docked Components.
50497      * @param {String} cqSelector A {@link Ext.ComponentQuery ComponentQuery} selector string to filter the returned items.
50498      * @return {Ext.Component[]} An array of components.
50499      */
50500     getDockedItems : function(cqSelector) {
50501         var me = this,
50502             defaultWeight = me.defaultDockWeights,
50503             dockedItems;
50504
50505         if (me.dockedItems && me.dockedItems.items.length) {
50506             // Allow filtering of returned docked items by CQ selector.
50507             if (cqSelector) {
50508                 dockedItems = Ext.ComponentQuery.query(cqSelector, me.dockedItems.items);
50509             } else {
50510                 dockedItems = me.dockedItems.items.slice();
50511             }
50512
50513             Ext.Array.sort(dockedItems, function(a, b) {
50514                 // Docked items are ordered by their visual representation by default (t,l,r,b)
50515                 var aw = a.weight || defaultWeight[a.dock],
50516                     bw = b.weight || defaultWeight[b.dock];
50517                 if (Ext.isNumber(aw) && Ext.isNumber(bw)) {
50518                     return aw - bw;
50519                 }
50520                 return 0;
50521             });
50522
50523             return dockedItems;
50524         }
50525         return [];
50526     },
50527
50528     // inherit docs
50529     addUIClsToElement: function(cls, force) {
50530         var me = this,
50531             result = me.callParent(arguments),
50532             classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
50533             array, i;
50534
50535         if (!force && me.rendered) {
50536             if (me.bodyCls) {
50537                 me.body.addCls(me.bodyCls);
50538             } else {
50539                 me.body.addCls(classes);
50540             }
50541         } else {
50542             if (me.bodyCls) {
50543                 array = me.bodyCls.split(' ');
50544
50545                 for (i = 0; i < classes.length; i++) {
50546                     if (!Ext.Array.contains(array, classes[i])) {
50547                         array.push(classes[i]);
50548                     }
50549                 }
50550
50551                 me.bodyCls = array.join(' ');
50552             } else {
50553                 me.bodyCls = classes.join(' ');
50554             }
50555         }
50556
50557         return result;
50558     },
50559
50560     // inherit docs
50561     removeUIClsFromElement: function(cls, force) {
50562         var me = this,
50563             result = me.callParent(arguments),
50564             classes = [Ext.baseCSSPrefix + cls, me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
50565             array, i;
50566
50567         if (!force && me.rendered) {
50568             if (me.bodyCls) {
50569                 me.body.removeCls(me.bodyCls);
50570             } else {
50571                 me.body.removeCls(classes);
50572             }
50573         } else {
50574             if (me.bodyCls) {
50575                 array = me.bodyCls.split(' ');
50576
50577                 for (i = 0; i < classes.length; i++) {
50578                     Ext.Array.remove(array, classes[i]);
50579                 }
50580
50581                 me.bodyCls = array.join(' ');
50582             }
50583         }
50584
50585         return result;
50586     },
50587
50588     // inherit docs
50589     addUIToElement: function(force) {
50590         var me = this,
50591             cls = me.baseCls + '-body-' + me.ui,
50592             array;
50593
50594         me.callParent(arguments);
50595
50596         if (!force && me.rendered) {
50597             if (me.bodyCls) {
50598                 me.body.addCls(me.bodyCls);
50599             } else {
50600                 me.body.addCls(cls);
50601             }
50602         } else {
50603             if (me.bodyCls) {
50604                 array = me.bodyCls.split(' ');
50605
50606                 if (!Ext.Array.contains(array, cls)) {
50607                     array.push(cls);
50608                 }
50609
50610                 me.bodyCls = array.join(' ');
50611             } else {
50612                 me.bodyCls = cls;
50613             }
50614         }
50615     },
50616
50617     // inherit docs
50618     removeUIFromElement: function() {
50619         var me = this,
50620             cls = me.baseCls + '-body-' + me.ui,
50621             array;
50622
50623         me.callParent(arguments);
50624
50625         if (me.rendered) {
50626             if (me.bodyCls) {
50627                 me.body.removeCls(me.bodyCls);
50628             } else {
50629                 me.body.removeCls(cls);
50630             }
50631         } else {
50632             if (me.bodyCls) {
50633                 array = me.bodyCls.split(' ');
50634                 Ext.Array.remove(array, cls);
50635                 me.bodyCls = array.join(' ');
50636             } else {
50637                 me.bodyCls = cls;
50638             }
50639         }
50640     },
50641
50642     // @private
50643     getTargetEl : function() {
50644         return this.body;
50645     },
50646
50647     getRefItems: function(deep) {
50648         var items = this.callParent(arguments),
50649             // deep fetches all docked items, and their descendants using '*' selector and then '* *'
50650             dockedItems = this.getDockedItems(deep ? '*,* *' : undefined),
50651             ln = dockedItems.length,
50652             i = 0,
50653             item;
50654
50655         // Find the index where we go from top/left docked items to right/bottom docked items
50656         for (; i < ln; i++) {
50657             item = dockedItems[i];
50658             if (item.dock === 'right' || item.dock === 'bottom') {
50659                 break;
50660             }
50661         }
50662
50663         // Return docked items in the top/left position before our container items, and
50664         // return right/bottom positioned items after our container items.
50665         // See AbstractDock.renderItems() for more information.
50666         return Ext.Array.splice(dockedItems, 0, i).concat(items).concat(dockedItems);
50667     },
50668
50669     beforeDestroy: function(){
50670         var docked = this.dockedItems,
50671             c;
50672
50673         if (docked) {
50674             while ((c = docked.first())) {
50675                 this.removeDocked(c, true);
50676             }
50677         }
50678         this.callParent();
50679     },
50680
50681     setBorder: function(border) {
50682         var me = this;
50683         me.border = (border !== undefined) ? border : true;
50684         if (me.rendered) {
50685             me.doComponentLayout();
50686         }
50687     }
50688 });
50689 /**
50690  * @class Ext.panel.Header
50691  * @extends Ext.container.Container
50692  * Simple header class which is used for on {@link Ext.panel.Panel} and {@link Ext.window.Window}
50693  */
50694 Ext.define('Ext.panel.Header', {
50695     extend: 'Ext.container.Container',
50696     uses: ['Ext.panel.Tool', 'Ext.draw.Component', 'Ext.util.CSS'],
50697     alias: 'widget.header',
50698
50699     isHeader       : true,
50700     defaultType    : 'tool',
50701     indicateDrag   : false,
50702     weight         : -1,
50703
50704     renderTpl: [
50705         '<div id="{id}-body" class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl>',
50706         '<tpl if="uiCls">',
50707             '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>',
50708         '</tpl>"',
50709         '<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>></div>'],
50710
50711     /**
50712      * @cfg {String} title
50713      * The title text to display
50714      */
50715
50716     /**
50717      * @cfg {String} iconCls
50718      * CSS class for icon in header. Used for displaying an icon to the left of a title.
50719      */
50720
50721     initComponent: function() {
50722         var me = this,
50723             ruleStyle,
50724             rule,
50725             style,
50726             titleTextEl,
50727             ui;
50728
50729         me.indicateDragCls = me.baseCls + '-draggable';
50730         me.title = me.title || '&#160;';
50731         me.tools = me.tools || [];
50732         me.items = me.items || [];
50733         me.orientation = me.orientation || 'horizontal';
50734         me.dock = (me.dock) ? me.dock : (me.orientation == 'horizontal') ? 'top' : 'left';
50735
50736         //add the dock as a ui
50737         //this is so we support top/right/left/bottom headers
50738         me.addClsWithUI(me.orientation);
50739         me.addClsWithUI(me.dock);
50740
50741         me.addChildEls('body');
50742
50743         // Add Icon
50744         if (!Ext.isEmpty(me.iconCls)) {
50745             me.initIconCmp();
50746             me.items.push(me.iconCmp);
50747         }
50748
50749         // Add Title
50750         if (me.orientation == 'vertical') {
50751             // Hack for IE6/7's inability to display an inline-block
50752             if (Ext.isIE6 || Ext.isIE7) {
50753                 me.width = this.width || 24;
50754             } else if (Ext.isIEQuirks) {
50755                 me.width = this.width || 25;
50756             }
50757
50758             me.layout = {
50759                 type : 'vbox',
50760                 align: 'center',
50761                 clearInnerCtOnLayout: true,
50762                 bindToOwnerCtContainer: false
50763             };
50764             me.textConfig = {
50765                 cls: me.baseCls + '-text',
50766                 type: 'text',
50767                 text: me.title,
50768                 rotate: {
50769                     degrees: 90
50770                 }
50771             };
50772             ui = me.ui;
50773             if (Ext.isArray(ui)) {
50774                 ui = ui[0];
50775             }
50776             ruleStyle = '.' + me.baseCls + '-text-' + ui;
50777             if (Ext.scopeResetCSS) {
50778                 ruleStyle = '.' + Ext.baseCSSPrefix + 'reset ' + ruleStyle;
50779             }
50780             rule = Ext.util.CSS.getRule(ruleStyle);
50781             if (rule) {
50782                 style = rule.style;
50783             }
50784             if (style) {
50785                 Ext.apply(me.textConfig, {
50786                     'font-family': style.fontFamily,
50787                     'font-weight': style.fontWeight,
50788                     'font-size': style.fontSize,
50789                     fill: style.color
50790                 });
50791             }
50792             me.titleCmp = Ext.create('Ext.draw.Component', {
50793                 ariaRole  : 'heading',
50794                 focusable: false,
50795                 viewBox: false,
50796                 flex : 1,
50797                 autoSize: true,
50798                 margins: '5 0 0 0',
50799                 items: [ me.textConfig ],
50800                 // this is a bit of a cheat: we are not selecting an element of titleCmp
50801                 // but rather of titleCmp.items[0] (so we cannot use childEls)
50802                 renderSelectors: {
50803                     textEl: '.' + me.baseCls + '-text'
50804                 }
50805             });
50806         } else {
50807             me.layout = {
50808                 type : 'hbox',
50809                 align: 'middle',
50810                 clearInnerCtOnLayout: true,
50811                 bindToOwnerCtContainer: false
50812             };
50813             me.titleCmp = Ext.create('Ext.Component', {
50814                 xtype     : 'component',
50815                 ariaRole  : 'heading',
50816                 focusable: false,
50817                 flex : 1,
50818                 cls: me.baseCls + '-text-container',
50819                 renderTpl : [
50820                     '<span id="{id}-textEl" class="{cls}-text {cls}-text-{ui}">{title}</span>'
50821                 ],
50822                 renderData: {
50823                     title: me.title,
50824                     cls  : me.baseCls,
50825                     ui   : me.ui
50826                 },
50827                 childEls: ['textEl']
50828             });
50829         }
50830         me.items.push(me.titleCmp);
50831
50832         // Add Tools
50833         me.items = me.items.concat(me.tools);
50834         this.callParent();
50835     },
50836
50837     initIconCmp: function() {
50838         this.iconCmp = Ext.create('Ext.Component', {
50839             focusable: false,
50840             renderTpl : [
50841                 '<img id="{id}-iconEl" alt="" src="{blank}" class="{cls}-icon {iconCls}"/>'
50842             ],
50843             renderData: {
50844                 blank  : Ext.BLANK_IMAGE_URL,
50845                 cls    : this.baseCls,
50846                 iconCls: this.iconCls,
50847                 orientation: this.orientation
50848             },
50849             childEls: ['iconEl'],
50850             iconCls: this.iconCls
50851         });
50852     },
50853
50854     afterRender: function() {
50855         var me = this;
50856
50857         me.el.unselectable();
50858         if (me.indicateDrag) {
50859             me.el.addCls(me.indicateDragCls);
50860         }
50861         me.mon(me.el, {
50862             click: me.onClick,
50863             scope: me
50864         });
50865         me.callParent();
50866     },
50867
50868     afterLayout: function() {
50869         var me = this;
50870         me.callParent(arguments);
50871
50872         // IE7 needs a forced repaint to make the top framing div expand to full width
50873         if (Ext.isIE7) {
50874             me.el.repaint();
50875         }
50876     },
50877
50878     // inherit docs
50879     addUIClsToElement: function(cls, force) {
50880         var me = this,
50881             result = me.callParent(arguments),
50882             classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
50883             array, i;
50884
50885         if (!force && me.rendered) {
50886             if (me.bodyCls) {
50887                 me.body.addCls(me.bodyCls);
50888             } else {
50889                 me.body.addCls(classes);
50890             }
50891         } else {
50892             if (me.bodyCls) {
50893                 array = me.bodyCls.split(' ');
50894
50895                 for (i = 0; i < classes.length; i++) {
50896                     if (!Ext.Array.contains(array, classes[i])) {
50897                         array.push(classes[i]);
50898                     }
50899                 }
50900
50901                 me.bodyCls = array.join(' ');
50902             } else {
50903                 me.bodyCls = classes.join(' ');
50904             }
50905         }
50906
50907         return result;
50908     },
50909
50910     // inherit docs
50911     removeUIClsFromElement: function(cls, force) {
50912         var me = this,
50913             result = me.callParent(arguments),
50914             classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
50915             array, i;
50916
50917         if (!force && me.rendered) {
50918             if (me.bodyCls) {
50919                 me.body.removeCls(me.bodyCls);
50920             } else {
50921                 me.body.removeCls(classes);
50922             }
50923         } else {
50924             if (me.bodyCls) {
50925                 array = me.bodyCls.split(' ');
50926
50927                 for (i = 0; i < classes.length; i++) {
50928                     Ext.Array.remove(array, classes[i]);
50929                 }
50930
50931                 me.bodyCls = array.join(' ');
50932             }
50933         }
50934
50935        return result;
50936     },
50937
50938     // inherit docs
50939     addUIToElement: function(force) {
50940         var me = this,
50941             array, cls;
50942
50943         me.callParent(arguments);
50944
50945         cls = me.baseCls + '-body-' + me.ui;
50946         if (!force && me.rendered) {
50947             if (me.bodyCls) {
50948                 me.body.addCls(me.bodyCls);
50949             } else {
50950                 me.body.addCls(cls);
50951             }
50952         } else {
50953             if (me.bodyCls) {
50954                 array = me.bodyCls.split(' ');
50955
50956                 if (!Ext.Array.contains(array, cls)) {
50957                     array.push(cls);
50958                 }
50959
50960                 me.bodyCls = array.join(' ');
50961             } else {
50962                 me.bodyCls = cls;
50963             }
50964         }
50965
50966         if (!force && me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
50967             me.titleCmp.textEl.addCls(me.baseCls + '-text-' + me.ui);
50968         }
50969     },
50970
50971     // inherit docs
50972     removeUIFromElement: function() {
50973         var me = this,
50974             array, cls;
50975
50976         me.callParent(arguments);
50977
50978         cls = me.baseCls + '-body-' + me.ui;
50979         if (me.rendered) {
50980             if (me.bodyCls) {
50981                 me.body.removeCls(me.bodyCls);
50982             } else {
50983                 me.body.removeCls(cls);
50984             }
50985         } else {
50986             if (me.bodyCls) {
50987                 array = me.bodyCls.split(' ');
50988                 Ext.Array.remove(array, cls);
50989                 me.bodyCls = array.join(' ');
50990             } else {
50991                 me.bodyCls = cls;
50992             }
50993         }
50994
50995         if (me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
50996             me.titleCmp.textEl.removeCls(me.baseCls + '-text-' + me.ui);
50997         }
50998     },
50999
51000     onClick: function(e) {
51001         if (!e.getTarget(Ext.baseCSSPrefix + 'tool')) {
51002             this.fireEvent('click', e);
51003         }
51004     },
51005
51006     getTargetEl: function() {
51007         return this.body || this.frameBody || this.el;
51008     },
51009
51010     /**
51011      * Sets the title of the header.
51012      * @param {String} title The title to be set
51013      */
51014     setTitle: function(title) {
51015         var me = this;
51016         if (me.rendered) {
51017             if (me.titleCmp.rendered) {
51018                 if (me.titleCmp.surface) {
51019                     me.title = title || '';
51020                     var sprite = me.titleCmp.surface.items.items[0],
51021                         surface = me.titleCmp.surface;
51022
51023                     surface.remove(sprite);
51024                     me.textConfig.type = 'text';
51025                     me.textConfig.text = title;
51026                     sprite = surface.add(me.textConfig);
51027                     sprite.setAttributes({
51028                         rotate: {
51029                             degrees: 90
51030                         }
51031                     }, true);
51032                     me.titleCmp.autoSizeSurface();
51033                 } else {
51034                     me.title = title || '&#160;';
51035                     me.titleCmp.textEl.update(me.title);
51036                 }
51037             } else {
51038                 me.titleCmp.on({
51039                     render: function() {
51040                         me.setTitle(title);
51041                     },
51042                     single: true
51043                 });
51044             }
51045         } else {
51046             me.on({
51047                 render: function() {
51048                     me.layout.layout();
51049                     me.setTitle(title);
51050                 },
51051                 single: true
51052             });
51053         }
51054     },
51055
51056     /**
51057      * Sets the CSS class that provides the icon image for this header.  This method will replace any existing
51058      * icon class if one has already been set.
51059      * @param {String} cls The new CSS class name
51060      */
51061     setIconCls: function(cls) {
51062         var me = this,
51063             isEmpty = !cls || !cls.length,
51064             iconCmp = me.iconCmp,
51065             el;
51066         
51067         me.iconCls = cls;
51068         if (!me.iconCmp && !isEmpty) {
51069             me.initIconCmp();
51070             me.insert(0, me.iconCmp);
51071         } else if (iconCmp) {
51072             if (isEmpty) {
51073                 me.iconCmp.destroy();
51074             } else {
51075                 el = iconCmp.iconEl;
51076                 el.removeCls(iconCmp.iconCls);
51077                 el.addCls(cls);
51078                 iconCmp.iconCls = cls;
51079             }
51080         }
51081     },
51082
51083     /**
51084      * Add a tool to the header
51085      * @param {Object} tool
51086      */
51087     addTool: function(tool) {
51088         this.tools.push(this.add(tool));
51089     },
51090
51091     /**
51092      * @private
51093      * Set up the tools.&lt;tool type> link in the owning Panel.
51094      * Bind the tool to its owning Panel.
51095      * @param component
51096      * @param index
51097      */
51098     onAdd: function(component, index) {
51099         this.callParent([arguments]);
51100         if (component instanceof Ext.panel.Tool) {
51101             component.bindTo(this.ownerCt);
51102             this.tools[component.type] = component;
51103         }
51104     }
51105 });
51106
51107 /**
51108  * @class Ext.fx.target.Element
51109  * @extends Ext.fx.target.Target
51110  * 
51111  * This class represents a animation target for an {@link Ext.Element}. In general this class will not be
51112  * created directly, the {@link Ext.Element} will be passed to the animation and
51113  * and the appropriate target will be created.
51114  */
51115 Ext.define('Ext.fx.target.Element', {
51116
51117     /* Begin Definitions */
51118     
51119     extend: 'Ext.fx.target.Target',
51120     
51121     /* End Definitions */
51122
51123     type: 'element',
51124
51125     getElVal: function(el, attr, val) {
51126         if (val == undefined) {
51127             if (attr === 'x') {
51128                 val = el.getX();
51129             }
51130             else if (attr === 'y') {
51131                 val = el.getY();
51132             }
51133             else if (attr === 'scrollTop') {
51134                 val = el.getScroll().top;
51135             }
51136             else if (attr === 'scrollLeft') {
51137                 val = el.getScroll().left;
51138             }
51139             else if (attr === 'height') {
51140                 val = el.getHeight();
51141             }
51142             else if (attr === 'width') {
51143                 val = el.getWidth();
51144             }
51145             else {
51146                 val = el.getStyle(attr);
51147             }
51148         }
51149         return val;
51150     },
51151
51152     getAttr: function(attr, val) {
51153         var el = this.target;
51154         return [[ el, this.getElVal(el, attr, val)]];
51155     },
51156
51157     setAttr: function(targetData) {
51158         var target = this.target,
51159             ln = targetData.length,
51160             attrs, attr, o, i, j, ln2, element, value;
51161         for (i = 0; i < ln; i++) {
51162             attrs = targetData[i].attrs;
51163             for (attr in attrs) {
51164                 if (attrs.hasOwnProperty(attr)) {
51165                     ln2 = attrs[attr].length;
51166                     for (j = 0; j < ln2; j++) {
51167                         o = attrs[attr][j];
51168                         element = o[0];
51169                         value = o[1];
51170                         if (attr === 'x') {
51171                             element.setX(value);
51172                         }
51173                         else if (attr === 'y') {
51174                             element.setY(value);
51175                         }
51176                         else if (attr === 'scrollTop') {
51177                             element.scrollTo('top', value);
51178                         }
51179                         else if (attr === 'scrollLeft') {
51180                             element.scrollTo('left',value);
51181                         }
51182                         else {
51183                             element.setStyle(attr, value);
51184                         }
51185                     }
51186                 }
51187             }
51188         }
51189     }
51190 });
51191
51192 /**
51193  * @class Ext.fx.target.CompositeElement
51194  * @extends Ext.fx.target.Element
51195  * 
51196  * This class represents a animation target for a {@link Ext.CompositeElement}. It allows
51197  * each {@link Ext.Element} in the group to be animated as a whole. In general this class will not be
51198  * created directly, the {@link Ext.CompositeElement} will be passed to the animation and
51199  * and the appropriate target will be created.
51200  */
51201 Ext.define('Ext.fx.target.CompositeElement', {
51202
51203     /* Begin Definitions */
51204
51205     extend: 'Ext.fx.target.Element',
51206
51207     /* End Definitions */
51208
51209     isComposite: true,
51210     
51211     constructor: function(target) {
51212         target.id = target.id || Ext.id(null, 'ext-composite-');
51213         this.callParent([target]);
51214     },
51215
51216     getAttr: function(attr, val) {
51217         var out = [],
51218             target = this.target;
51219         target.each(function(el) {
51220             out.push([el, this.getElVal(el, attr, val)]);
51221         }, this);
51222         return out;
51223     }
51224 });
51225
51226 /**
51227  * @class Ext.fx.Manager
51228  * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis.
51229  * @private
51230  * @singleton
51231  */
51232
51233 Ext.define('Ext.fx.Manager', {
51234
51235     /* Begin Definitions */
51236
51237     singleton: true,
51238
51239     requires: ['Ext.util.MixedCollection',
51240                'Ext.fx.target.Element',
51241                'Ext.fx.target.CompositeElement',
51242                'Ext.fx.target.Sprite',
51243                'Ext.fx.target.CompositeSprite',
51244                'Ext.fx.target.Component'],
51245
51246     mixins: {
51247         queue: 'Ext.fx.Queue'
51248     },
51249
51250     /* End Definitions */
51251
51252     constructor: function() {
51253         this.items = Ext.create('Ext.util.MixedCollection');
51254         this.mixins.queue.constructor.call(this);
51255
51256         // this.requestAnimFrame = (function() {
51257         //     var raf = window.requestAnimationFrame ||
51258         //               window.webkitRequestAnimationFrame ||
51259         //               window.mozRequestAnimationFrame ||
51260         //               window.oRequestAnimationFrame ||
51261         //               window.msRequestAnimationFrame;
51262         //     if (raf) {
51263         //         return function(callback, element) {
51264         //             raf(callback);
51265         //         };
51266         //     }
51267         //     else {
51268         //         return function(callback, element) {
51269         //             window.setTimeout(callback, Ext.fx.Manager.interval);
51270         //         };
51271         //     }
51272         // })();
51273     },
51274
51275     /**
51276      * @cfg {Number} interval Default interval in miliseconds to calculate each frame.  Defaults to 16ms (~60fps)
51277      */
51278     interval: 16,
51279
51280     /**
51281      * @cfg {Boolean} forceJS Turn off to not use CSS3 transitions when they are available
51282      */
51283     forceJS: true,
51284
51285     // @private Target factory
51286     createTarget: function(target) {
51287         var me = this,
51288             useCSS3 = !me.forceJS && Ext.supports.Transitions,
51289             targetObj;
51290
51291         me.useCSS3 = useCSS3;
51292
51293         // dom id
51294         if (Ext.isString(target)) {
51295             target = Ext.get(target);
51296         }
51297         // dom element
51298         if (target && target.tagName) {
51299             target = Ext.get(target);
51300             targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
51301             me.targets.add(targetObj);
51302             return targetObj;
51303         }
51304         if (Ext.isObject(target)) {
51305             // Element
51306             if (target.dom) {
51307                 targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
51308             }
51309             // Element Composite
51310             else if (target.isComposite) {
51311                 targetObj = Ext.create('Ext.fx.target.' + 'CompositeElement' + (useCSS3 ? 'CSS' : ''), target);
51312             }
51313             // Draw Sprite
51314             else if (target.isSprite) {
51315                 targetObj = Ext.create('Ext.fx.target.Sprite', target);
51316             }
51317             // Draw Sprite Composite
51318             else if (target.isCompositeSprite) {
51319                 targetObj = Ext.create('Ext.fx.target.CompositeSprite', target);
51320             }
51321             // Component
51322             else if (target.isComponent) {
51323                 targetObj = Ext.create('Ext.fx.target.Component', target);
51324             }
51325             else if (target.isAnimTarget) {
51326                 return target;
51327             }
51328             else {
51329                 return null;
51330             }
51331             me.targets.add(targetObj);
51332             return targetObj;
51333         }
51334         else {
51335             return null;
51336         }
51337     },
51338
51339     /**
51340      * Add an Anim to the manager. This is done automatically when an Anim instance is created.
51341      * @param {Ext.fx.Anim} anim
51342      */
51343     addAnim: function(anim) {
51344         var items = this.items,
51345             task = this.task;
51346         // var me = this,
51347         //     items = me.items,
51348         //     cb = function() {
51349         //         if (items.length) {
51350         //             me.task = true;
51351         //             me.runner();
51352         //             me.requestAnimFrame(cb);
51353         //         }
51354         //         else {
51355         //             me.task = false;
51356         //         }
51357         //     };
51358
51359         items.add(anim);
51360
51361         // Start the timer if not already running
51362         if (!task && items.length) {
51363             task = this.task = {
51364                 run: this.runner,
51365                 interval: this.interval,
51366                 scope: this
51367             };
51368             Ext.TaskManager.start(task);
51369         }
51370
51371         // //Start the timer if not already running
51372         // if (!me.task && items.length) {
51373         //     me.requestAnimFrame(cb);
51374         // }
51375     },
51376
51377     /**
51378      * Remove an Anim from the manager. This is done automatically when an Anim ends.
51379      * @param {Ext.fx.Anim} anim
51380      */
51381     removeAnim: function(anim) {
51382         // this.items.remove(anim);
51383         var items = this.items,
51384             task = this.task;
51385         items.remove(anim);
51386         // Stop the timer if there are no more managed Anims
51387         if (task && !items.length) {
51388             Ext.TaskManager.stop(task);
51389             delete this.task;
51390         }
51391     },
51392
51393     /**
51394      * @private
51395      * Filter function to determine which animations need to be started
51396      */
51397     startingFilter: function(o) {
51398         return o.paused === false && o.running === false && o.iterations > 0;
51399     },
51400
51401     /**
51402      * @private
51403      * Filter function to determine which animations are still running
51404      */
51405     runningFilter: function(o) {
51406         return o.paused === false && o.running === true && o.isAnimator !== true;
51407     },
51408
51409     /**
51410      * @private
51411      * Runner function being called each frame
51412      */
51413     runner: function() {
51414         var me = this,
51415             items = me.items;
51416
51417         me.targetData = {};
51418         me.targetArr = {};
51419
51420         // Single timestamp for all animations this interval
51421         me.timestamp = new Date();
51422
51423         // Start any items not current running
51424         items.filterBy(me.startingFilter).each(me.startAnim, me);
51425
51426         // Build the new attributes to be applied for all targets in this frame
51427         items.filterBy(me.runningFilter).each(me.runAnim, me);
51428
51429         // Apply all the pending changes to their targets
51430         me.applyPendingAttrs();
51431     },
51432
51433     /**
51434      * @private
51435      * Start the individual animation (initialization)
51436      */
51437     startAnim: function(anim) {
51438         anim.start(this.timestamp);
51439     },
51440
51441     /**
51442      * @private
51443      * Run the individual animation for this frame
51444      */
51445     runAnim: function(anim) {
51446         if (!anim) {
51447             return;
51448         }
51449         var me = this,
51450             targetId = anim.target.getId(),
51451             useCSS3 = me.useCSS3 && anim.target.type == 'element',
51452             elapsedTime = me.timestamp - anim.startTime,
51453             target, o;
51454
51455         this.collectTargetData(anim, elapsedTime, useCSS3);
51456
51457         // For CSS3 animation, we need to immediately set the first frame's attributes without any transition
51458         // to get a good initial state, then add the transition properties and set the final attributes.
51459         if (useCSS3) {
51460             // Flush the collected attributes, without transition
51461             anim.target.setAttr(me.targetData[targetId], true);
51462
51463             // Add the end frame data
51464             me.targetData[targetId] = [];
51465             me.collectTargetData(anim, anim.duration, useCSS3);
51466
51467             // Pause the animation so runAnim doesn't keep getting called
51468             anim.paused = true;
51469
51470             target = anim.target.target;
51471             // We only want to attach an event on the last element in a composite
51472             if (anim.target.isComposite) {
51473                 target = anim.target.target.last();
51474             }
51475
51476             // Listen for the transitionend event
51477             o = {};
51478             o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame;
51479             o.scope = anim;
51480             o.single = true;
51481             target.on(o);
51482         }
51483         // For JS animation, trigger the lastFrame handler if this is the final frame
51484         else if (elapsedTime >= anim.duration) {
51485             me.applyPendingAttrs(true);
51486             delete me.targetData[targetId];
51487             delete me.targetArr[targetId];
51488             anim.lastFrame();
51489         }
51490     },
51491
51492     /**
51493      * Collect target attributes for the given Anim object at the given timestamp
51494      * @param {Ext.fx.Anim} anim The Anim instance
51495      * @param {Number} timestamp Time after the anim's start time
51496      */
51497     collectTargetData: function(anim, elapsedTime, useCSS3) {
51498         var targetId = anim.target.getId(),
51499             targetData = this.targetData[targetId],
51500             data;
51501         
51502         if (!targetData) {
51503             targetData = this.targetData[targetId] = [];
51504             this.targetArr[targetId] = anim.target;
51505         }
51506
51507         data = {
51508             duration: anim.duration,
51509             easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing,
51510             attrs: {}
51511         };
51512         Ext.apply(data.attrs, anim.runAnim(elapsedTime));
51513         targetData.push(data);
51514     },
51515
51516     /**
51517      * @private
51518      * Apply all pending attribute changes to their targets
51519      */
51520     applyPendingAttrs: function(isLastFrame) {
51521         var targetData = this.targetData,
51522             targetArr = this.targetArr,
51523             targetId;
51524         for (targetId in targetData) {
51525             if (targetData.hasOwnProperty(targetId)) {
51526                 targetArr[targetId].setAttr(targetData[targetId], false, isLastFrame);
51527             }
51528         }
51529     }
51530 });
51531
51532 /**
51533  * @class Ext.fx.Animator
51534  *
51535  * This class is used to run keyframe based animations, which follows the CSS3 based animation structure.
51536  * Keyframe animations differ from typical from/to animations in that they offer the ability to specify values
51537  * at various points throughout the animation.
51538  *
51539  * ## Using Keyframes
51540  *
51541  * The {@link #keyframes} option is the most important part of specifying an animation when using this
51542  * class. A key frame is a point in a particular animation. We represent this as a percentage of the
51543  * total animation duration. At each key frame, we can specify the target values at that time. Note that
51544  * you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link #keyframe}
51545  * event that fires after each key frame is reached.
51546  *
51547  * ## Example
51548  *
51549  * In the example below, we modify the values of the element at each fifth throughout the animation.
51550  *
51551  *     @example
51552  *     Ext.create('Ext.fx.Animator', {
51553  *         target: Ext.getBody().createChild({
51554  *             style: {
51555  *                 width: '100px',
51556  *                 height: '100px',
51557  *                 'background-color': 'red'
51558  *             }
51559  *         }),
51560  *         duration: 10000, // 10 seconds
51561  *         keyframes: {
51562  *             0: {
51563  *                 opacity: 1,
51564  *                 backgroundColor: 'FF0000'
51565  *             },
51566  *             20: {
51567  *                 x: 30,
51568  *                 opacity: 0.5
51569  *             },
51570  *             40: {
51571  *                 x: 130,
51572  *                 backgroundColor: '0000FF'
51573  *             },
51574  *             60: {
51575  *                 y: 80,
51576  *                 opacity: 0.3
51577  *             },
51578  *             80: {
51579  *                 width: 200,
51580  *                 y: 200
51581  *             },
51582  *             100: {
51583  *                 opacity: 1,
51584  *                 backgroundColor: '00FF00'
51585  *             }
51586  *         }
51587  *     });
51588  */
51589 Ext.define('Ext.fx.Animator', {
51590
51591     /* Begin Definitions */
51592
51593     mixins: {
51594         observable: 'Ext.util.Observable'
51595     },
51596
51597     requires: ['Ext.fx.Manager'],
51598
51599     /* End Definitions */
51600
51601     isAnimator: true,
51602
51603     /**
51604      * @cfg {Number} duration
51605      * Time in milliseconds for the animation to last. Defaults to 250.
51606      */
51607     duration: 250,
51608
51609     /**
51610      * @cfg {Number} delay
51611      * Time to delay before starting the animation. Defaults to 0.
51612      */
51613     delay: 0,
51614
51615     /* private used to track a delayed starting time */
51616     delayStart: 0,
51617
51618     /**
51619      * @cfg {Boolean} dynamic
51620      * 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.
51621      */
51622     dynamic: false,
51623
51624     /**
51625      * @cfg {String} easing
51626      *
51627      * This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
51628      * speed over its duration.
51629      *
51630      *  - backIn
51631      *  - backOut
51632      *  - bounceIn
51633      *  - bounceOut
51634      *  - ease
51635      *  - easeIn
51636      *  - easeOut
51637      *  - easeInOut
51638      *  - elasticIn
51639      *  - elasticOut
51640      *  - cubic-bezier(x1, y1, x2, y2)
51641      *
51642      * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
51643      * specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
51644      * be in the range [0, 1] or the definition is invalid.
51645      *
51646      * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
51647      */
51648     easing: 'ease',
51649
51650     /**
51651      * Flag to determine if the animation has started
51652      * @property running
51653      * @type Boolean
51654      */
51655     running: false,
51656
51657     /**
51658      * Flag to determine if the animation is paused. Only set this to true if you need to
51659      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
51660      * @property paused
51661      * @type Boolean
51662      */
51663     paused: false,
51664
51665     /**
51666      * @private
51667      */
51668     damper: 1,
51669
51670     /**
51671      * @cfg {Number} iterations
51672      * Number of times to execute the animation. Defaults to 1.
51673      */
51674     iterations: 1,
51675
51676     /**
51677      * Current iteration the animation is running.
51678      * @property currentIteration
51679      * @type Number
51680      */
51681     currentIteration: 0,
51682
51683     /**
51684      * Current keyframe step of the animation.
51685      * @property keyframeStep
51686      * @type Number
51687      */
51688     keyframeStep: 0,
51689
51690     /**
51691      * @private
51692      */
51693     animKeyFramesRE: /^(from|to|\d+%?)$/,
51694
51695     /**
51696      * @cfg {Ext.fx.target.Target} target
51697      * The Ext.fx.target to apply the animation to.  If not specified during initialization, this can be passed to the applyAnimator
51698      * method to apply the same animation to many targets.
51699      */
51700
51701      /**
51702       * @cfg {Object} keyframes
51703       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
51704       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
51705       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
51706       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
51707       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
51708  <pre><code>
51709 keyframes : {
51710     '0%': {
51711         left: 100
51712     },
51713     '40%': {
51714         left: 150
51715     },
51716     '60%': {
51717         left: 75
51718     },
51719     '100%': {
51720         left: 100
51721     }
51722 }
51723  </code></pre>
51724       */
51725     constructor: function(config) {
51726         var me = this;
51727         config = Ext.apply(me, config || {});
51728         me.config = config;
51729         me.id = Ext.id(null, 'ext-animator-');
51730         me.addEvents(
51731             /**
51732              * @event beforeanimate
51733              * Fires before the animation starts. A handler can return false to cancel the animation.
51734              * @param {Ext.fx.Animator} this
51735              */
51736             'beforeanimate',
51737             /**
51738               * @event keyframe
51739               * Fires at each keyframe.
51740               * @param {Ext.fx.Animator} this
51741               * @param {Number} keyframe step number
51742               */
51743             'keyframe',
51744             /**
51745              * @event afteranimate
51746              * Fires when the animation is complete.
51747              * @param {Ext.fx.Animator} this
51748              * @param {Date} startTime
51749              */
51750             'afteranimate'
51751         );
51752         me.mixins.observable.constructor.call(me, config);
51753         me.timeline = [];
51754         me.createTimeline(me.keyframes);
51755         if (me.target) {
51756             me.applyAnimator(me.target);
51757             Ext.fx.Manager.addAnim(me);
51758         }
51759     },
51760
51761     /**
51762      * @private
51763      */
51764     sorter: function (a, b) {
51765         return a.pct - b.pct;
51766     },
51767
51768     /**
51769      * @private
51770      * Takes the given keyframe configuration object and converts it into an ordered array with the passed attributes per keyframe
51771      * or applying the 'to' configuration to all keyframes.  Also calculates the proper animation duration per keyframe.
51772      */
51773     createTimeline: function(keyframes) {
51774         var me = this,
51775             attrs = [],
51776             to = me.to || {},
51777             duration = me.duration,
51778             prevMs, ms, i, ln, pct, anim, nextAnim, attr;
51779
51780         for (pct in keyframes) {
51781             if (keyframes.hasOwnProperty(pct) && me.animKeyFramesRE.test(pct)) {
51782                 attr = {attrs: Ext.apply(keyframes[pct], to)};
51783                 // CSS3 spec allow for from/to to be specified.
51784                 if (pct == "from") {
51785                     pct = 0;
51786                 }
51787                 else if (pct == "to") {
51788                     pct = 100;
51789                 }
51790                 // convert % values into integers
51791                 attr.pct = parseInt(pct, 10);
51792                 attrs.push(attr);
51793             }
51794         }
51795         // Sort by pct property
51796         Ext.Array.sort(attrs, me.sorter);
51797         // Only an end
51798         //if (attrs[0].pct) {
51799         //    attrs.unshift({pct: 0, attrs: element.attrs});
51800         //}
51801
51802         ln = attrs.length;
51803         for (i = 0; i < ln; i++) {
51804             prevMs = (attrs[i - 1]) ? duration * (attrs[i - 1].pct / 100) : 0;
51805             ms = duration * (attrs[i].pct / 100);
51806             me.timeline.push({
51807                 duration: ms - prevMs,
51808                 attrs: attrs[i].attrs
51809             });
51810         }
51811     },
51812
51813     /**
51814      * Applies animation to the Ext.fx.target
51815      * @private
51816      * @param target
51817      * @type String/Object
51818      */
51819     applyAnimator: function(target) {
51820         var me = this,
51821             anims = [],
51822             timeline = me.timeline,
51823             reverse = me.reverse,
51824             ln = timeline.length,
51825             anim, easing, damper, initial, attrs, lastAttrs, i;
51826
51827         if (me.fireEvent('beforeanimate', me) !== false) {
51828             for (i = 0; i < ln; i++) {
51829                 anim = timeline[i];
51830                 attrs = anim.attrs;
51831                 easing = attrs.easing || me.easing;
51832                 damper = attrs.damper || me.damper;
51833                 delete attrs.easing;
51834                 delete attrs.damper;
51835                 anim = Ext.create('Ext.fx.Anim', {
51836                     target: target,
51837                     easing: easing,
51838                     damper: damper,
51839                     duration: anim.duration,
51840                     paused: true,
51841                     to: attrs
51842                 });
51843                 anims.push(anim);
51844             }
51845             me.animations = anims;
51846             me.target = anim.target;
51847             for (i = 0; i < ln - 1; i++) {
51848                 anim = anims[i];
51849                 anim.nextAnim = anims[i + 1];
51850                 anim.on('afteranimate', function() {
51851                     this.nextAnim.paused = false;
51852                 });
51853                 anim.on('afteranimate', function() {
51854                     this.fireEvent('keyframe', this, ++this.keyframeStep);
51855                 }, me);
51856             }
51857             anims[ln - 1].on('afteranimate', function() {
51858                 this.lastFrame();
51859             }, me);
51860         }
51861     },
51862
51863     /**
51864      * @private
51865      * Fires beforeanimate and sets the running flag.
51866      */
51867     start: function(startTime) {
51868         var me = this,
51869             delay = me.delay,
51870             delayStart = me.delayStart,
51871             delayDelta;
51872         if (delay) {
51873             if (!delayStart) {
51874                 me.delayStart = startTime;
51875                 return;
51876             }
51877             else {
51878                 delayDelta = startTime - delayStart;
51879                 if (delayDelta < delay) {
51880                     return;
51881                 }
51882                 else {
51883                     // Compensate for frame delay;
51884                     startTime = new Date(delayStart.getTime() + delay);
51885                 }
51886             }
51887         }
51888         if (me.fireEvent('beforeanimate', me) !== false) {
51889             me.startTime = startTime;
51890             me.running = true;
51891             me.animations[me.keyframeStep].paused = false;
51892         }
51893     },
51894
51895     /**
51896      * @private
51897      * Perform lastFrame cleanup and handle iterations
51898      * @returns a hash of the new attributes.
51899      */
51900     lastFrame: function() {
51901         var me = this,
51902             iter = me.iterations,
51903             iterCount = me.currentIteration;
51904
51905         iterCount++;
51906         if (iterCount < iter) {
51907             me.startTime = new Date();
51908             me.currentIteration = iterCount;
51909             me.keyframeStep = 0;
51910             me.applyAnimator(me.target);
51911             me.animations[me.keyframeStep].paused = false;
51912         }
51913         else {
51914             me.currentIteration = 0;
51915             me.end();
51916         }
51917     },
51918
51919     /**
51920      * Fire afteranimate event and end the animation. Usually called automatically when the
51921      * animation reaches its final frame, but can also be called manually to pre-emptively
51922      * stop and destroy the running animation.
51923      */
51924     end: function() {
51925         var me = this;
51926         me.fireEvent('afteranimate', me, me.startTime, new Date() - me.startTime);
51927     }
51928 });
51929 /**
51930  * @class Ext.fx.Easing
51931  *
51932  * This class contains a series of function definitions used to modify values during an animation.
51933  * They describe how the intermediate values used during a transition will be calculated. It allows for a transition to change
51934  * speed over its duration. The following options are available: 
51935  *
51936  * - linear The default easing type
51937  * - backIn
51938  * - backOut
51939  * - bounceIn
51940  * - bounceOut
51941  * - ease
51942  * - easeIn
51943  * - easeOut
51944  * - easeInOut
51945  * - elasticIn
51946  * - elasticOut
51947  * - cubic-bezier(x1, y1, x2, y2)
51948  *
51949  * Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
51950  * specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
51951  * be in the range [0, 1] or the definition is invalid.
51952  *
51953  * [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
51954  *
51955  * @singleton
51956  */
51957 Ext.ns('Ext.fx');
51958
51959 Ext.require('Ext.fx.CubicBezier', function() {
51960     var math = Math,
51961         pi = math.PI,
51962         pow = math.pow,
51963         sin = math.sin,
51964         sqrt = math.sqrt,
51965         abs = math.abs,
51966         backInSeed = 1.70158;
51967     Ext.fx.Easing = {
51968         // ease: Ext.fx.CubicBezier.cubicBezier(0.25, 0.1, 0.25, 1),
51969         // linear: Ext.fx.CubicBezier.cubicBezier(0, 0, 1, 1),
51970         // 'ease-in': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
51971         // 'ease-out': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
51972         // 'ease-in-out': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1),
51973         // 'easeIn': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
51974         // 'easeOut': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
51975         // 'easeInOut': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1)
51976     };
51977
51978     Ext.apply(Ext.fx.Easing, {
51979         linear: function(n) {
51980             return n;
51981         },
51982         ease: function(n) {
51983             var q = 0.07813 - n / 2,
51984                 alpha = -0.25,
51985                 Q = sqrt(0.0066 + q * q),
51986                 x = Q - q,
51987                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
51988                 y = -Q - q,
51989                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
51990                 t = X + Y + 0.25;
51991             return pow(1 - t, 2) * 3 * t * 0.1 + (1 - t) * 3 * t * t + t * t * t;
51992         },
51993         easeIn: function (n) {
51994             return pow(n, 1.7);
51995         },
51996         easeOut: function (n) {
51997             return pow(n, 0.48);
51998         },
51999         easeInOut: function(n) {
52000             var q = 0.48 - n / 1.04,
52001                 Q = sqrt(0.1734 + q * q),
52002                 x = Q - q,
52003                 X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
52004                 y = -Q - q,
52005                 Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
52006                 t = X + Y + 0.5;
52007             return (1 - t) * 3 * t * t + t * t * t;
52008         },
52009         backIn: function (n) {
52010             return n * n * ((backInSeed + 1) * n - backInSeed);
52011         },
52012         backOut: function (n) {
52013             n = n - 1;
52014             return n * n * ((backInSeed + 1) * n + backInSeed) + 1;
52015         },
52016         elasticIn: function (n) {
52017             if (n === 0 || n === 1) {
52018                 return n;
52019             }
52020             var p = 0.3,
52021                 s = p / 4;
52022             return pow(2, -10 * n) * sin((n - s) * (2 * pi) / p) + 1;
52023         },
52024         elasticOut: function (n) {
52025             return 1 - Ext.fx.Easing.elasticIn(1 - n);
52026         },
52027         bounceIn: function (n) {
52028             return 1 - Ext.fx.Easing.bounceOut(1 - n);
52029         },
52030         bounceOut: function (n) {
52031             var s = 7.5625,
52032                 p = 2.75,
52033                 l;
52034             if (n < (1 / p)) {
52035                 l = s * n * n;
52036             } else {
52037                 if (n < (2 / p)) {
52038                     n -= (1.5 / p);
52039                     l = s * n * n + 0.75;
52040                 } else {
52041                     if (n < (2.5 / p)) {
52042                         n -= (2.25 / p);
52043                         l = s * n * n + 0.9375;
52044                     } else {
52045                         n -= (2.625 / p);
52046                         l = s * n * n + 0.984375;
52047                     }
52048                 }
52049             }
52050             return l;
52051         }
52052     });
52053     Ext.apply(Ext.fx.Easing, {
52054         'back-in': Ext.fx.Easing.backIn,
52055         'back-out': Ext.fx.Easing.backOut,
52056         'ease-in': Ext.fx.Easing.easeIn,
52057         'ease-out': Ext.fx.Easing.easeOut,
52058         'elastic-in': Ext.fx.Easing.elasticIn,
52059         'elastic-out': Ext.fx.Easing.elasticIn,
52060         'bounce-in': Ext.fx.Easing.bounceIn,
52061         'bounce-out': Ext.fx.Easing.bounceOut,
52062         'ease-in-out': Ext.fx.Easing.easeInOut
52063     });
52064 });
52065 /**
52066  * @class Ext.draw.Draw
52067  * Base Drawing class.  Provides base drawing functions.
52068  * @private
52069  */
52070 Ext.define('Ext.draw.Draw', {
52071     /* Begin Definitions */
52072
52073     singleton: true,
52074
52075     requires: ['Ext.draw.Color'],
52076
52077     /* End Definitions */
52078
52079     pathToStringRE: /,?([achlmqrstvxz]),?/gi,
52080     pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
52081     pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
52082     stopsRE: /^(\d+%?)$/,
52083     radian: Math.PI / 180,
52084
52085     availableAnimAttrs: {
52086         along: "along",
52087         blur: null,
52088         "clip-rect": "csv",
52089         cx: null,
52090         cy: null,
52091         fill: "color",
52092         "fill-opacity": null,
52093         "font-size": null,
52094         height: null,
52095         opacity: null,
52096         path: "path",
52097         r: null,
52098         rotation: "csv",
52099         rx: null,
52100         ry: null,
52101         scale: "csv",
52102         stroke: "color",
52103         "stroke-opacity": null,
52104         "stroke-width": null,
52105         translation: "csv",
52106         width: null,
52107         x: null,
52108         y: null
52109     },
52110
52111     is: function(o, type) {
52112         type = String(type).toLowerCase();
52113         return (type == "object" && o === Object(o)) ||
52114             (type == "undefined" && typeof o == type) ||
52115             (type == "null" && o === null) ||
52116             (type == "array" && Array.isArray && Array.isArray(o)) ||
52117             (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
52118     },
52119
52120     ellipsePath: function(sprite) {
52121         var attr = sprite.attr;
52122         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);
52123     },
52124
52125     rectPath: function(sprite) {
52126         var attr = sprite.attr;
52127         if (attr.radius) {
52128             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);
52129         }
52130         else {
52131             return Ext.String.format("M{0},{1}l{2},0,0,{3},{4},0z", attr.x, attr.y, attr.width, attr.height, -attr.width);
52132         }
52133     },
52134
52135     // To be deprecated, converts itself (an arrayPath) to a proper SVG path string
52136     path2string: function () {
52137         return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
52138     },
52139
52140     // Convert the passed arrayPath to a proper SVG path string (d attribute)
52141     pathToString: function(arrayPath) {
52142         return arrayPath.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
52143     },
52144
52145     parsePathString: function (pathString) {
52146         if (!pathString) {
52147             return null;
52148         }
52149         var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
52150             data = [],
52151             me = this;
52152         if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
52153             data = me.pathClone(pathString);
52154         }
52155         if (!data.length) {
52156             String(pathString).replace(me.pathCommandRE, function (a, b, c) {
52157                 var params = [],
52158                     name = b.toLowerCase();
52159                 c.replace(me.pathValuesRE, function (a, b) {
52160                     b && params.push(+b);
52161                 });
52162                 if (name == "m" && params.length > 2) {
52163                     data.push([b].concat(Ext.Array.splice(params, 0, 2)));
52164                     name = "l";
52165                     b = (b == "m") ? "l" : "L";
52166                 }
52167                 while (params.length >= paramCounts[name]) {
52168                     data.push([b].concat(Ext.Array.splice(params, 0, paramCounts[name])));
52169                     if (!paramCounts[name]) {
52170                         break;
52171                     }
52172                 }
52173             });
52174         }
52175         data.toString = me.path2string;
52176         return data;
52177     },
52178
52179     mapPath: function (path, matrix) {
52180         if (!matrix) {
52181             return path;
52182         }
52183         var x, y, i, ii, j, jj, pathi;
52184         path = this.path2curve(path);
52185         for (i = 0, ii = path.length; i < ii; i++) {
52186             pathi = path[i];
52187             for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
52188                 x = matrix.x(pathi[j], pathi[j + 1]);
52189                 y = matrix.y(pathi[j], pathi[j + 1]);
52190                 pathi[j] = x;
52191                 pathi[j + 1] = y;
52192             }
52193         }
52194         return path;
52195     },
52196
52197     pathClone: function(pathArray) {
52198         var res = [],
52199             j, jj, i, ii;
52200         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
52201             pathArray = this.parsePathString(pathArray);
52202         }
52203         for (i = 0, ii = pathArray.length; i < ii; i++) {
52204             res[i] = [];
52205             for (j = 0, jj = pathArray[i].length; j < jj; j++) {
52206                 res[i][j] = pathArray[i][j];
52207             }
52208         }
52209         res.toString = this.path2string;
52210         return res;
52211     },
52212
52213     pathToAbsolute: function (pathArray) {
52214         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
52215             pathArray = this.parsePathString(pathArray);
52216         }
52217         var res = [],
52218             x = 0,
52219             y = 0,
52220             mx = 0,
52221             my = 0,
52222             i = 0,
52223             ln = pathArray.length,
52224             r, pathSegment, j, ln2;
52225         // MoveTo initial x/y position
52226         if (ln && pathArray[0][0] == "M") {
52227             x = +pathArray[0][1];
52228             y = +pathArray[0][2];
52229             mx = x;
52230             my = y;
52231             i++;
52232             res[0] = ["M", x, y];
52233         }
52234         for (; i < ln; i++) {
52235             r = res[i] = [];
52236             pathSegment = pathArray[i];
52237             if (pathSegment[0] != pathSegment[0].toUpperCase()) {
52238                 r[0] = pathSegment[0].toUpperCase();
52239                 switch (r[0]) {
52240                     // Elliptical Arc
52241                     case "A":
52242                         r[1] = pathSegment[1];
52243                         r[2] = pathSegment[2];
52244                         r[3] = pathSegment[3];
52245                         r[4] = pathSegment[4];
52246                         r[5] = pathSegment[5];
52247                         r[6] = +(pathSegment[6] + x);
52248                         r[7] = +(pathSegment[7] + y);
52249                         break;
52250                     // Vertical LineTo
52251                     case "V":
52252                         r[1] = +pathSegment[1] + y;
52253                         break;
52254                     // Horizontal LineTo
52255                     case "H":
52256                         r[1] = +pathSegment[1] + x;
52257                         break;
52258                     case "M":
52259                     // MoveTo
52260                         mx = +pathSegment[1] + x;
52261                         my = +pathSegment[2] + y;
52262                     default:
52263                         j = 1;
52264                         ln2 = pathSegment.length;
52265                         for (; j < ln2; j++) {
52266                             r[j] = +pathSegment[j] + ((j % 2) ? x : y);
52267                         }
52268                 }
52269             }
52270             else {
52271                 j = 0;
52272                 ln2 = pathSegment.length;
52273                 for (; j < ln2; j++) {
52274                     res[i][j] = pathSegment[j];
52275                 }
52276             }
52277             switch (r[0]) {
52278                 // ClosePath
52279                 case "Z":
52280                     x = mx;
52281                     y = my;
52282                     break;
52283                 // Horizontal LineTo
52284                 case "H":
52285                     x = r[1];
52286                     break;
52287                 // Vertical LineTo
52288                 case "V":
52289                     y = r[1];
52290                     break;
52291                 // MoveTo
52292                 case "M":
52293                     pathSegment = res[i];
52294                     ln2 = pathSegment.length;
52295                     mx = pathSegment[ln2 - 2];
52296                     my = pathSegment[ln2 - 1];
52297                 default:
52298                     pathSegment = res[i];
52299                     ln2 = pathSegment.length;
52300                     x = pathSegment[ln2 - 2];
52301                     y = pathSegment[ln2 - 1];
52302             }
52303         }
52304         res.toString = this.path2string;
52305         return res;
52306     },
52307
52308     // TO BE DEPRECATED
52309     pathToRelative: function (pathArray) {
52310         if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
52311             pathArray = this.parsePathString(pathArray);
52312         }
52313         var res = [],
52314             x = 0,
52315             y = 0,
52316             mx = 0,
52317             my = 0,
52318             start = 0;
52319         if (pathArray[0][0] == "M") {
52320             x = pathArray[0][1];
52321             y = pathArray[0][2];
52322             mx = x;
52323             my = y;
52324             start++;
52325             res.push(["M", x, y]);
52326         }
52327         for (var i = start, ii = pathArray.length; i < ii; i++) {
52328             var r = res[i] = [],
52329                 pa = pathArray[i];
52330             if (pa[0] != pa[0].toLowerCase()) {
52331                 r[0] = pa[0].toLowerCase();
52332                 switch (r[0]) {
52333                     case "a":
52334                         r[1] = pa[1];
52335                         r[2] = pa[2];
52336                         r[3] = pa[3];
52337                         r[4] = pa[4];
52338                         r[5] = pa[5];
52339                         r[6] = +(pa[6] - x).toFixed(3);
52340                         r[7] = +(pa[7] - y).toFixed(3);
52341                         break;
52342                     case "v":
52343                         r[1] = +(pa[1] - y).toFixed(3);
52344                         break;
52345                     case "m":
52346                         mx = pa[1];
52347                         my = pa[2];
52348                     default:
52349                         for (var j = 1, jj = pa.length; j < jj; j++) {
52350                             r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
52351                         }
52352                 }
52353             } else {
52354                 r = res[i] = [];
52355                 if (pa[0] == "m") {
52356                     mx = pa[1] + x;
52357                     my = pa[2] + y;
52358                 }
52359                 for (var k = 0, kk = pa.length; k < kk; k++) {
52360                     res[i][k] = pa[k];
52361                 }
52362             }
52363             var len = res[i].length;
52364             switch (res[i][0]) {
52365                 case "z":
52366                     x = mx;
52367                     y = my;
52368                     break;
52369                 case "h":
52370                     x += +res[i][len - 1];
52371                     break;
52372                 case "v":
52373                     y += +res[i][len - 1];
52374                     break;
52375                 default:
52376                     x += +res[i][len - 2];
52377                     y += +res[i][len - 1];
52378             }
52379         }
52380         res.toString = this.path2string;
52381         return res;
52382     },
52383
52384     // Returns a path converted to a set of curveto commands
52385     path2curve: function (path) {
52386         var me = this,
52387             points = me.pathToAbsolute(path),
52388             ln = points.length,
52389             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
52390             i, seg, segLn, point;
52391             
52392         for (i = 0; i < ln; i++) {
52393             points[i] = me.command2curve(points[i], attrs);
52394             if (points[i].length > 7) {
52395                     points[i].shift();
52396                     point = points[i];
52397                     while (point.length) {
52398                         Ext.Array.splice(points, i++, 0, ["C"].concat(Ext.Array.splice(point, 0, 6)));
52399                     }
52400                     Ext.Array.erase(points, i, 1);
52401                     ln = points.length;
52402                 }
52403             seg = points[i];
52404             segLn = seg.length;
52405             attrs.x = seg[segLn - 2];
52406             attrs.y = seg[segLn - 1];
52407             attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
52408             attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
52409         }
52410         return points;
52411     },
52412     
52413     interpolatePaths: function (path, path2) {
52414         var me = this,
52415             p = me.pathToAbsolute(path),
52416             p2 = me.pathToAbsolute(path2),
52417             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
52418             attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
52419             fixArc = function (pp, i) {
52420                 if (pp[i].length > 7) {
52421                     pp[i].shift();
52422                     var pi = pp[i];
52423                     while (pi.length) {
52424                         Ext.Array.splice(pp, i++, 0, ["C"].concat(Ext.Array.splice(pi, 0, 6)));
52425                     }
52426                     Ext.Array.erase(pp, i, 1);
52427                     ii = Math.max(p.length, p2.length || 0);
52428                 }
52429             },
52430             fixM = function (path1, path2, a1, a2, i) {
52431                 if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
52432                     Ext.Array.splice(path2, i, 0, ["M", a2.x, a2.y]);
52433                     a1.bx = 0;
52434                     a1.by = 0;
52435                     a1.x = path1[i][1];
52436                     a1.y = path1[i][2];
52437                     ii = Math.max(p.length, p2.length || 0);
52438                 }
52439             };
52440         for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
52441             p[i] = me.command2curve(p[i], attrs);
52442             fixArc(p, i);
52443             (p2[i] = me.command2curve(p2[i], attrs2));
52444             fixArc(p2, i);
52445             fixM(p, p2, attrs, attrs2, i);
52446             fixM(p2, p, attrs2, attrs, i);
52447             var seg = p[i],
52448                 seg2 = p2[i],
52449                 seglen = seg.length,
52450                 seg2len = seg2.length;
52451             attrs.x = seg[seglen - 2];
52452             attrs.y = seg[seglen - 1];
52453             attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
52454             attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
52455             attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
52456             attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
52457             attrs2.x = seg2[seg2len - 2];
52458             attrs2.y = seg2[seg2len - 1];
52459         }
52460         return [p, p2];
52461     },
52462     
52463     //Returns any path command as a curveto command based on the attrs passed
52464     command2curve: function (pathCommand, d) {
52465         var me = this;
52466         if (!pathCommand) {
52467             return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
52468         }
52469         if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
52470             d.qx = d.qy = null;
52471         }
52472         switch (pathCommand[0]) {
52473             case "M":
52474                 d.X = pathCommand[1];
52475                 d.Y = pathCommand[2];
52476                 break;
52477             case "A":
52478                 pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
52479                 break;
52480             case "S":
52481                 pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
52482                 break;
52483             case "T":
52484                 d.qx = d.x + (d.x - (d.qx || d.x));
52485                 d.qy = d.y + (d.y - (d.qy || d.y));
52486                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
52487                 break;
52488             case "Q":
52489                 d.qx = pathCommand[1];
52490                 d.qy = pathCommand[2];
52491                 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
52492                 break;
52493             case "L":
52494                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
52495                 break;
52496             case "H":
52497                 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
52498                 break;
52499             case "V":
52500                 pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
52501                 break;
52502             case "Z":
52503                 pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
52504                 break;
52505         }
52506         return pathCommand;
52507     },
52508
52509     quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
52510         var _13 = 1 / 3,
52511             _23 = 2 / 3;
52512         return [
52513                 _13 * x1 + _23 * ax,
52514                 _13 * y1 + _23 * ay,
52515                 _13 * x2 + _23 * ax,
52516                 _13 * y2 + _23 * ay,
52517                 x2,
52518                 y2
52519             ];
52520     },
52521     
52522     rotate: function (x, y, rad) {
52523         var cos = Math.cos(rad),
52524             sin = Math.sin(rad),
52525             X = x * cos - y * sin,
52526             Y = x * sin + y * cos;
52527         return {x: X, y: Y};
52528     },
52529
52530     arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
52531         // for more information of where this Math came from visit:
52532         // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
52533         var me = this,
52534             PI = Math.PI,
52535             radian = me.radian,
52536             _120 = PI * 120 / 180,
52537             rad = radian * (+angle || 0),
52538             res = [],
52539             math = Math,
52540             mcos = math.cos,
52541             msin = math.sin,
52542             msqrt = math.sqrt,
52543             mabs = math.abs,
52544             masin = math.asin,
52545             xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
52546             t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
52547         if (!recursive) {
52548             xy = me.rotate(x1, y1, -rad);
52549             x1 = xy.x;
52550             y1 = xy.y;
52551             xy = me.rotate(x2, y2, -rad);
52552             x2 = xy.x;
52553             y2 = xy.y;
52554             cos = mcos(radian * angle);
52555             sin = msin(radian * angle);
52556             x = (x1 - x2) / 2;
52557             y = (y1 - y2) / 2;
52558             h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
52559             if (h > 1) {
52560                 h = msqrt(h);
52561                 rx = h * rx;
52562                 ry = h * ry;
52563             }
52564             rx2 = rx * rx;
52565             ry2 = ry * ry;
52566             k = (large_arc_flag == sweep_flag ? -1 : 1) *
52567                     msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
52568             cx = k * rx * y / ry + (x1 + x2) / 2;
52569             cy = k * -ry * x / rx + (y1 + y2) / 2;
52570             f1 = masin(((y1 - cy) / ry).toFixed(7));
52571             f2 = masin(((y2 - cy) / ry).toFixed(7));
52572
52573             f1 = x1 < cx ? PI - f1 : f1;
52574             f2 = x2 < cx ? PI - f2 : f2;
52575             if (f1 < 0) {
52576                 f1 = PI * 2 + f1;
52577             }
52578             if (f2 < 0) {
52579                 f2 = PI * 2 + f2;
52580             }
52581             if (sweep_flag && f1 > f2) {
52582                 f1 = f1 - PI * 2;
52583             }
52584             if (!sweep_flag && f2 > f1) {
52585                 f2 = f2 - PI * 2;
52586             }
52587         }
52588         else {
52589             f1 = recursive[0];
52590             f2 = recursive[1];
52591             cx = recursive[2];
52592             cy = recursive[3];
52593         }
52594         df = f2 - f1;
52595         if (mabs(df) > _120) {
52596             f2old = f2;
52597             x2old = x2;
52598             y2old = y2;
52599             f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
52600             x2 = cx + rx * mcos(f2);
52601             y2 = cy + ry * msin(f2);
52602             res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
52603         }
52604         df = f2 - f1;
52605         c1 = mcos(f1);
52606         s1 = msin(f1);
52607         c2 = mcos(f2);
52608         s2 = msin(f2);
52609         t = math.tan(df / 4);
52610         hx = 4 / 3 * rx * t;
52611         hy = 4 / 3 * ry * t;
52612         m1 = [x1, y1];
52613         m2 = [x1 + hx * s1, y1 - hy * c1];
52614         m3 = [x2 + hx * s2, y2 - hy * c2];
52615         m4 = [x2, y2];
52616         m2[0] = 2 * m1[0] - m2[0];
52617         m2[1] = 2 * m1[1] - m2[1];
52618         if (recursive) {
52619             return [m2, m3, m4].concat(res);
52620         }
52621         else {
52622             res = [m2, m3, m4].concat(res).join().split(",");
52623             newres = [];
52624             ln = res.length;
52625             for (i = 0;  i < ln; i++) {
52626                 newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
52627             }
52628             return newres;
52629         }
52630     },
52631
52632     // TO BE DEPRECATED
52633     rotateAndTranslatePath: function (sprite) {
52634         var alpha = sprite.rotation.degrees,
52635             cx = sprite.rotation.x,
52636             cy = sprite.rotation.y,
52637             dx = sprite.translation.x,
52638             dy = sprite.translation.y,
52639             path,
52640             i,
52641             p,
52642             xy,
52643             j,
52644             res = [];
52645         if (!alpha && !dx && !dy) {
52646             return this.pathToAbsolute(sprite.attr.path);
52647         }
52648         dx = dx || 0;
52649         dy = dy || 0;
52650         path = this.pathToAbsolute(sprite.attr.path);
52651         for (i = path.length; i--;) {
52652             p = res[i] = path[i].slice();
52653             if (p[0] == "A") {
52654                 xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
52655                 p[6] = xy.x + dx;
52656                 p[7] = xy.y + dy;
52657             } else {
52658                 j = 1;
52659                 while (p[j + 1] != null) {
52660                     xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
52661                     p[j] = xy.x + dx;
52662                     p[j + 1] = xy.y + dy;
52663                     j += 2;
52664                 }
52665             }
52666         }
52667         return res;
52668     },
52669
52670     // TO BE DEPRECATED
52671     rotatePoint: function (x, y, alpha, cx, cy) {
52672         if (!alpha) {
52673             return {
52674                 x: x,
52675                 y: y
52676             };
52677         }
52678         cx = cx || 0;
52679         cy = cy || 0;
52680         x = x - cx;
52681         y = y - cy;
52682         alpha = alpha * this.radian;
52683         var cos = Math.cos(alpha),
52684             sin = Math.sin(alpha);
52685         return {
52686             x: x * cos - y * sin + cx,
52687             y: x * sin + y * cos + cy
52688         };
52689     },
52690
52691     pathDimensions: function (path) {
52692         if (!path || !(path + "")) {
52693             return {x: 0, y: 0, width: 0, height: 0};
52694         }
52695         path = this.path2curve(path);
52696         var x = 0, 
52697             y = 0,
52698             X = [],
52699             Y = [],
52700             i = 0,
52701             ln = path.length,
52702             p, xmin, ymin, dim;
52703         for (; i < ln; i++) {
52704             p = path[i];
52705             if (p[0] == "M") {
52706                 x = p[1];
52707                 y = p[2];
52708                 X.push(x);
52709                 Y.push(y);
52710             }
52711             else {
52712                 dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
52713                 X = X.concat(dim.min.x, dim.max.x);
52714                 Y = Y.concat(dim.min.y, dim.max.y);
52715                 x = p[5];
52716                 y = p[6];
52717             }
52718         }
52719         xmin = Math.min.apply(0, X);
52720         ymin = Math.min.apply(0, Y);
52721         return {
52722             x: xmin,
52723             y: ymin,
52724             path: path,
52725             width: Math.max.apply(0, X) - xmin,
52726             height: Math.max.apply(0, Y) - ymin
52727         };
52728     },
52729
52730     intersectInside: function(path, cp1, cp2) {
52731         return (cp2[0] - cp1[0]) * (path[1] - cp1[1]) > (cp2[1] - cp1[1]) * (path[0] - cp1[0]);
52732     },
52733
52734     intersectIntersection: function(s, e, cp1, cp2) {
52735         var p = [],
52736             dcx = cp1[0] - cp2[0],
52737             dcy = cp1[1] - cp2[1],
52738             dpx = s[0] - e[0],
52739             dpy = s[1] - e[1],
52740             n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0],
52741             n2 = s[0] * e[1] - s[1] * e[0],
52742             n3 = 1 / (dcx * dpy - dcy * dpx);
52743
52744         p[0] = (n1 * dpx - n2 * dcx) * n3;
52745         p[1] = (n1 * dpy - n2 * dcy) * n3;
52746         return p;
52747     },
52748
52749     intersect: function(subjectPolygon, clipPolygon) {
52750         var me = this,
52751             i = 0,
52752             ln = clipPolygon.length,
52753             cp1 = clipPolygon[ln - 1],
52754             outputList = subjectPolygon,
52755             cp2, s, e, point, ln2, inputList, j;
52756         for (; i < ln; ++i) {
52757             cp2 = clipPolygon[i];
52758             inputList = outputList;
52759             outputList = [];
52760             s = inputList[inputList.length - 1];
52761             j = 0;
52762             ln2 = inputList.length;
52763             for (; j < ln2; j++) {
52764                 e = inputList[j];
52765                 if (me.intersectInside(e, cp1, cp2)) {
52766                     if (!me.intersectInside(s, cp1, cp2)) {
52767                         outputList.push(me.intersectIntersection(s, e, cp1, cp2));
52768                     }
52769                     outputList.push(e);
52770                 }
52771                 else if (me.intersectInside(s, cp1, cp2)) {
52772                     outputList.push(me.intersectIntersection(s, e, cp1, cp2));
52773                 }
52774                 s = e;
52775             }
52776             cp1 = cp2;
52777         }
52778         return outputList;
52779     },
52780
52781     curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
52782         var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
52783             b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
52784             c = p1x - c1x,
52785             t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
52786             t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
52787             y = [p1y, p2y],
52788             x = [p1x, p2x],
52789             dot;
52790         if (Math.abs(t1) > 1e12) {
52791             t1 = 0.5;
52792         }
52793         if (Math.abs(t2) > 1e12) {
52794             t2 = 0.5;
52795         }
52796         if (t1 > 0 && t1 < 1) {
52797             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
52798             x.push(dot.x);
52799             y.push(dot.y);
52800         }
52801         if (t2 > 0 && t2 < 1) {
52802             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
52803             x.push(dot.x);
52804             y.push(dot.y);
52805         }
52806         a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
52807         b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
52808         c = p1y - c1y;
52809         t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
52810         t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
52811         if (Math.abs(t1) > 1e12) {
52812             t1 = 0.5;
52813         }
52814         if (Math.abs(t2) > 1e12) {
52815             t2 = 0.5;
52816         }
52817         if (t1 > 0 && t1 < 1) {
52818             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
52819             x.push(dot.x);
52820             y.push(dot.y);
52821         }
52822         if (t2 > 0 && t2 < 1) {
52823             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
52824             x.push(dot.x);
52825             y.push(dot.y);
52826         }
52827         return {
52828             min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
52829             max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
52830         };
52831     },
52832
52833     /**
52834      * @private
52835      *
52836      * Calculates bezier curve control anchor points for a particular point in a path, with a
52837      * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
52838      * Note that this algorithm assumes that the line being smoothed is normalized going from left
52839      * to right; it makes special adjustments assuming this orientation.
52840      *
52841      * @param {Number} prevX X coordinate of the previous point in the path
52842      * @param {Number} prevY Y coordinate of the previous point in the path
52843      * @param {Number} curX X coordinate of the current point in the path
52844      * @param {Number} curY Y coordinate of the current point in the path
52845      * @param {Number} nextX X coordinate of the next point in the path
52846      * @param {Number} nextY Y coordinate of the next point in the path
52847      * @param {Number} value A value to control the smoothness of the curve; this is used to
52848      * divide the distance between points, so a value of 2 corresponds to
52849      * half the distance between points (a very smooth line) while higher values
52850      * result in less smooth curves. Defaults to 4.
52851      * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
52852      * are the control point for the curve toward the previous path point, and
52853      * x2 and y2 are the control point for the curve toward the next path point.
52854      */
52855     getAnchors: function (prevX, prevY, curX, curY, nextX, nextY, value) {
52856         value = value || 4;
52857         var M = Math,
52858             PI = M.PI,
52859             halfPI = PI / 2,
52860             abs = M.abs,
52861             sin = M.sin,
52862             cos = M.cos,
52863             atan = M.atan,
52864             control1Length, control2Length, control1Angle, control2Angle,
52865             control1X, control1Y, control2X, control2Y, alpha;
52866
52867         // Find the length of each control anchor line, by dividing the horizontal distance
52868         // between points by the value parameter.
52869         control1Length = (curX - prevX) / value;
52870         control2Length = (nextX - curX) / value;
52871
52872         // Determine the angle of each control anchor line. If the middle point is a vertical
52873         // turnaround then we force it to a flat horizontal angle to prevent the curve from
52874         // dipping above or below the middle point. Otherwise we use an angle that points
52875         // toward the previous/next target point.
52876         if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
52877             control1Angle = control2Angle = halfPI;
52878         } else {
52879             control1Angle = atan((curX - prevX) / abs(curY - prevY));
52880             if (prevY < curY) {
52881                 control1Angle = PI - control1Angle;
52882             }
52883             control2Angle = atan((nextX - curX) / abs(curY - nextY));
52884             if (nextY < curY) {
52885                 control2Angle = PI - control2Angle;
52886             }
52887         }
52888
52889         // Adjust the calculated angles so they point away from each other on the same line
52890         alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
52891         if (alpha > halfPI) {
52892             alpha -= PI;
52893         }
52894         control1Angle += alpha;
52895         control2Angle += alpha;
52896
52897         // Find the control anchor points from the angles and length
52898         control1X = curX - control1Length * sin(control1Angle);
52899         control1Y = curY + control1Length * cos(control1Angle);
52900         control2X = curX + control2Length * sin(control2Angle);
52901         control2Y = curY + control2Length * cos(control2Angle);
52902
52903         // One last adjustment, make sure that no control anchor point extends vertically past
52904         // its target prev/next point, as that results in curves dipping above or below and
52905         // bending back strangely. If we find this happening we keep the control angle but
52906         // reduce the length of the control line so it stays within bounds.
52907         if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
52908             control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
52909             control1Y = prevY;
52910         }
52911         if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
52912             control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
52913             control2Y = nextY;
52914         }
52915         
52916         return {
52917             x1: control1X,
52918             y1: control1Y,
52919             x2: control2X,
52920             y2: control2Y
52921         };
52922     },
52923
52924     /* Smoothing function for a path.  Converts a path into cubic beziers.  Value defines the divider of the distance between points.
52925      * Defaults to a value of 4.
52926      */
52927     smooth: function (originalPath, value) {
52928         var path = this.path2curve(originalPath),
52929             newp = [path[0]],
52930             x = path[0][1],
52931             y = path[0][2],
52932             j,
52933             points,
52934             i = 1,
52935             ii = path.length,
52936             beg = 1,
52937             mx = x,
52938             my = y,
52939             cx = 0,
52940             cy = 0;
52941         for (; i < ii; i++) {
52942             var pathi = path[i],
52943                 pathil = pathi.length,
52944                 pathim = path[i - 1],
52945                 pathiml = pathim.length,
52946                 pathip = path[i + 1],
52947                 pathipl = pathip && pathip.length;
52948             if (pathi[0] == "M") {
52949                 mx = pathi[1];
52950                 my = pathi[2];
52951                 j = i + 1;
52952                 while (path[j][0] != "C") {
52953                     j++;
52954                 }
52955                 cx = path[j][5];
52956                 cy = path[j][6];
52957                 newp.push(["M", mx, my]);
52958                 beg = newp.length;
52959                 x = mx;
52960                 y = my;
52961                 continue;
52962             }
52963             if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
52964                 var begl = newp[beg].length;
52965                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
52966                 newp[beg][1] = points.x2;
52967                 newp[beg][2] = points.y2;
52968             }
52969             else if (!pathip || pathip[0] == "M") {
52970                 points = {
52971                     x1: pathi[pathil - 2],
52972                     y1: pathi[pathil - 1]
52973                 };
52974             } else {
52975                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
52976             }
52977             newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
52978             x = points.x2;
52979             y = points.y2;
52980         }
52981         return newp;
52982     },
52983
52984     findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
52985         var t1 = 1 - t;
52986         return {
52987             x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
52988             y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
52989         };
52990     },
52991
52992     /**
52993      * A utility method to deduce an appropriate tick configuration for the data set of given
52994      * feature.
52995      * 
52996      * @param {Number/Date} from The minimum value in the data
52997      * @param {Number/Date} to The maximum value in the data
52998      * @param {Number} stepsMax The maximum number of ticks
52999      * @return {Object} The calculated step and ends info; When `from` and `to` are Dates, refer to the
53000      * return value of {@link #snapEndsByDate}. For numerical `from` and `to` the return value contains:
53001      * @return {Number} return.from The result start value, which may be lower than the original start value
53002      * @return {Number} return.to The result end value, which may be higher than the original end value
53003      * @return {Number} return.power The calculate power.
53004      * @return {Number} return.step The value size of each step
53005      * @return {Number} return.steps The number of steps.
53006      */
53007     snapEnds: function (from, to, stepsMax) {
53008         if (Ext.isDate(from)) {
53009             return this.snapEndsByDate(from, to, stepsMax);
53010         }
53011         var step = (to - from) / stepsMax,
53012             level = Math.floor(Math.log(step) / Math.LN10) + 1,
53013             m = Math.pow(10, level),
53014             cur,
53015             modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
53016             interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
53017             stepCount = 0,
53018             value,
53019             weight,
53020             i,
53021             topValue,
53022             topWeight = 1e9,
53023             ln = interval.length;
53024         cur = from = Math.floor(from / m) * m;
53025         for (i = 0; i < ln; i++) {
53026             value = interval[i][0];
53027             weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
53028             if (weight < topWeight) {
53029                 topValue = value;
53030                 topWeight = weight;
53031             }
53032         }
53033         step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
53034         while (cur < to) {
53035             cur += step;
53036             stepCount++;
53037         }
53038         to = +cur.toFixed(10);
53039         return {
53040             from: from,
53041             to: to,
53042             power: level,
53043             step: step,
53044             steps: stepCount
53045         };
53046     },
53047
53048     /**
53049      * A utility method to deduce an appropriate tick configuration for the data set of given
53050      * feature when data is Dates. Refer to {@link #snapEnds} for numeric data.
53051      *
53052      * @param {Date} from The minimum value in the data
53053      * @param {Date} to The maximum value in the data
53054      * @param {Number} stepsMax The maximum number of ticks
53055      * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
53056      * and will not be adjusted
53057      * @return {Object} The calculated step and ends info; properties are:
53058      * @return {Date} return.from The result start value, which may be lower than the original start value
53059      * @return {Date} return.to The result end value, which may be higher than the original end value
53060      * @return {Number} return.step The value size of each step
53061      * @return {Number} return.steps The number of steps.
53062      * NOTE: the steps may not divide the from/to range perfectly evenly;
53063      * there may be a smaller distance between the last step and the end value than between prior
53064      * steps, particularly when the `endsLocked` param is true. Therefore it is best to not use
53065      * the `steps` result when finding the axis tick points, instead use the `step`, `to`, and
53066      * `from` to find the correct point for each tick.
53067      */
53068     snapEndsByDate: function (from, to, stepsMax, lockEnds) {
53069         var selectedStep = false, scales = [
53070                 [Ext.Date.MILLI, [1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500]],
53071                 [Ext.Date.SECOND, [1, 2, 3, 5, 10, 15, 30]],
53072                 [Ext.Date.MINUTE, [1, 2, 3, 5, 10, 20, 30]],
53073                 [Ext.Date.HOUR, [1, 2, 3, 4, 6, 12]],
53074                 [Ext.Date.DAY, [1, 2, 3, 7, 14]],
53075                 [Ext.Date.MONTH, [1, 2, 3, 4, 6]]
53076             ], j, yearDiff;
53077
53078         // Find the most desirable scale
53079         Ext.each(scales, function(scale, i) {
53080             for (j = 0; j < scale[1].length; j++) {
53081                 if (to < Ext.Date.add(from, scale[0], scale[1][j] * stepsMax)) {
53082                     selectedStep = [scale[0], scale[1][j]];
53083                     return false;
53084                 }
53085             }
53086         });
53087         if (!selectedStep) {
53088             yearDiff = this.snapEnds(from.getFullYear(), to.getFullYear() + 1, stepsMax, lockEnds);
53089             selectedStep = [Date.YEAR, Math.round(yearDiff.step)];
53090         }
53091         return this.snapEndsByDateAndStep(from, to, selectedStep, lockEnds);
53092     },
53093
53094
53095     /**
53096      * A utility method to deduce an appropriate tick configuration for the data set of given
53097      * feature and specific step size.
53098      * @param {Date} from The minimum value in the data
53099      * @param {Date} to The maximum value in the data
53100      * @param {Array} step An array with two components: The first is the unit of the step (day, month, year, etc).
53101      * The second one is the number of units for the step (1, 2, etc.).
53102      * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
53103      * and will not be adjusted
53104      * @return {Object} See the return value of {@link #snapEndsByDate}.
53105      */
53106     snapEndsByDateAndStep: function(from, to, step, lockEnds) {
53107         var fromStat = [from.getFullYear(), from.getMonth(), from.getDate(),
53108                 from.getHours(), from.getMinutes(), from.getSeconds(), from.getMilliseconds()],
53109             steps = 0, testFrom, testTo;
53110         if (lockEnds) {
53111             testFrom = from;
53112         } else {
53113             switch (step[0]) {
53114                 case Ext.Date.MILLI:
53115                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53116                             fromStat[4], fromStat[5], Math.floor(fromStat[6] / step[1]) * step[1]);
53117                     break;
53118                 case Ext.Date.SECOND:
53119                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53120                             fromStat[4], Math.floor(fromStat[5] / step[1]) * step[1], 0);
53121                     break;
53122                 case Ext.Date.MINUTE:
53123                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
53124                             Math.floor(fromStat[4] / step[1]) * step[1], 0, 0);
53125                     break;
53126                 case Ext.Date.HOUR:
53127                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2],
53128                             Math.floor(fromStat[3] / step[1]) * step[1], 0, 0, 0);
53129                     break;
53130                 case Ext.Date.DAY:
53131                     testFrom = new Date(fromStat[0], fromStat[1],
53132                             Math.floor(fromStat[2] - 1 / step[1]) * step[1] + 1, 0, 0, 0, 0);
53133                     break;
53134                 case Ext.Date.MONTH:
53135                     testFrom = new Date(fromStat[0], Math.floor(fromStat[1] / step[1]) * step[1], 1, 0, 0, 0, 0);
53136                     break;
53137                 default: // Ext.Date.YEAR
53138                     testFrom = new Date(Math.floor(fromStat[0] / step[1]) * step[1], 0, 1, 0, 0, 0, 0);
53139                     break;
53140             }
53141         }
53142
53143         testTo = testFrom;
53144         // TODO(zhangbei) : We can do it better somehow...
53145         while (testTo < to) {
53146             testTo = Ext.Date.add(testTo, step[0], step[1]);
53147             steps++;
53148         }
53149
53150         if (lockEnds) {
53151             testTo = to;
53152         }
53153         return {
53154             from : +testFrom,
53155             to : +testTo,
53156             step : (testTo - testFrom) / steps,
53157             steps : steps
53158         };
53159     },
53160
53161     sorter: function (a, b) {
53162         return a.offset - b.offset;
53163     },
53164
53165     rad: function(degrees) {
53166         return degrees % 360 * Math.PI / 180;
53167     },
53168
53169     degrees: function(radian) {
53170         return radian * 180 / Math.PI % 360;
53171     },
53172
53173     withinBox: function(x, y, bbox) {
53174         bbox = bbox || {};
53175         return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
53176     },
53177
53178     parseGradient: function(gradient) {
53179         var me = this,
53180             type = gradient.type || 'linear',
53181             angle = gradient.angle || 0,
53182             radian = me.radian,
53183             stops = gradient.stops,
53184             stopsArr = [],
53185             stop,
53186             vector,
53187             max,
53188             stopObj;
53189
53190         if (type == 'linear') {
53191             vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
53192             max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
53193             vector[2] *= max;
53194             vector[3] *= max;
53195             if (vector[2] < 0) {
53196                 vector[0] = -vector[2];
53197                 vector[2] = 0;
53198             }
53199             if (vector[3] < 0) {
53200                 vector[1] = -vector[3];
53201                 vector[3] = 0;
53202             }
53203         }
53204
53205         for (stop in stops) {
53206             if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
53207                 stopObj = {
53208                     offset: parseInt(stop, 10),
53209                     color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
53210                     opacity: stops[stop].opacity || 1
53211                 };
53212                 stopsArr.push(stopObj);
53213             }
53214         }
53215         // Sort by pct property
53216         Ext.Array.sort(stopsArr, me.sorter);
53217         if (type == 'linear') {
53218             return {
53219                 id: gradient.id,
53220                 type: type,
53221                 vector: vector,
53222                 stops: stopsArr
53223             };
53224         }
53225         else {
53226             return {
53227                 id: gradient.id,
53228                 type: type,
53229                 centerX: gradient.centerX,
53230                 centerY: gradient.centerY,
53231                 focalX: gradient.focalX,
53232                 focalY: gradient.focalY,
53233                 radius: gradient.radius,
53234                 vector: vector,
53235                 stops: stopsArr
53236             };
53237         }
53238     }
53239 });
53240
53241
53242 /**
53243  * @class Ext.fx.PropertyHandler
53244  * @ignore
53245  */
53246 Ext.define('Ext.fx.PropertyHandler', {
53247
53248     /* Begin Definitions */
53249
53250     requires: ['Ext.draw.Draw'],
53251
53252     statics: {
53253         defaultHandler: {
53254             pixelDefaultsRE: /width|height|top$|bottom$|left$|right$/i,
53255             unitRE: /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/,
53256             scrollRE: /^scroll/i,
53257
53258             computeDelta: function(from, end, damper, initial, attr) {
53259                 damper = (typeof damper == 'number') ? damper : 1;
53260                 var unitRE = this.unitRE,
53261                     match = unitRE.exec(from),
53262                     start, units;
53263                 if (match) {
53264                     from = match[1];
53265                     units = match[2];
53266                     if (!this.scrollRE.test(attr) && !units && this.pixelDefaultsRE.test(attr)) {
53267                         units = 'px';
53268                     }
53269                 }
53270                 from = +from || 0;
53271
53272                 match = unitRE.exec(end);
53273                 if (match) {
53274                     end = match[1];
53275                     units = match[2] || units;
53276                 }
53277                 end = +end || 0;
53278                 start = (initial != null) ? initial : from;
53279                 return {
53280                     from: from,
53281                     delta: (end - start) * damper,
53282                     units: units
53283                 };
53284             },
53285
53286             get: function(from, end, damper, initialFrom, attr) {
53287                 var ln = from.length,
53288                     out = [],
53289                     i, initial, res, j, len;
53290                 for (i = 0; i < ln; i++) {
53291                     if (initialFrom) {
53292                         initial = initialFrom[i][1].from;
53293                     }
53294                     if (Ext.isArray(from[i][1]) && Ext.isArray(end)) {
53295                         res = [];
53296                         j = 0;
53297                         len = from[i][1].length;
53298                         for (; j < len; j++) {
53299                             res.push(this.computeDelta(from[i][1][j], end[j], damper, initial, attr));
53300                         }
53301                         out.push([from[i][0], res]);
53302                     }
53303                     else {
53304                         out.push([from[i][0], this.computeDelta(from[i][1], end, damper, initial, attr)]);
53305                     }
53306                 }
53307                 return out;
53308             },
53309
53310             set: function(values, easing) {
53311                 var ln = values.length,
53312                     out = [],
53313                     i, val, res, len, j;
53314                 for (i = 0; i < ln; i++) {
53315                     val  = values[i][1];
53316                     if (Ext.isArray(val)) {
53317                         res = [];
53318                         j = 0;
53319                         len = val.length;
53320                         for (; j < len; j++) {
53321                             res.push(val[j].from + (val[j].delta * easing) + (val[j].units || 0));
53322                         }
53323                         out.push([values[i][0], res]);
53324                     } else {
53325                         out.push([values[i][0], val.from + (val.delta * easing) + (val.units || 0)]);
53326                     }
53327                 }
53328                 return out;
53329             }
53330         },
53331         color: {
53332             rgbRE: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
53333             hexRE: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
53334             hex3RE: /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i,
53335
53336             parseColor : function(color, damper) {
53337                 damper = (typeof damper == 'number') ? damper : 1;
53338                 var base,
53339                     out = false,
53340                     match;
53341
53342                 Ext.each([this.hexRE, this.rgbRE, this.hex3RE], function(re, idx) {
53343                     base = (idx % 2 == 0) ? 16 : 10;
53344                     match = re.exec(color);
53345                     if (match && match.length == 4) {
53346                         if (idx == 2) {
53347                             match[1] += match[1];
53348                             match[2] += match[2];
53349                             match[3] += match[3];
53350                         }
53351                         out = {
53352                             red: parseInt(match[1], base),
53353                             green: parseInt(match[2], base),
53354                             blue: parseInt(match[3], base)
53355                         };
53356                         return false;
53357                     }
53358                 });
53359                 return out || color;
53360             },
53361
53362             computeDelta: function(from, end, damper, initial) {
53363                 from = this.parseColor(from);
53364                 end = this.parseColor(end, damper);
53365                 var start = initial ? initial : from,
53366                     tfrom = typeof start,
53367                     tend = typeof end;
53368                 //Extra check for when the color string is not recognized.
53369                 if (tfrom == 'string' ||  tfrom == 'undefined' 
53370                   || tend == 'string' || tend == 'undefined') {
53371                     return end || start;
53372                 }
53373                 return {
53374                     from:  from,
53375                     delta: {
53376                         red: Math.round((end.red - start.red) * damper),
53377                         green: Math.round((end.green - start.green) * damper),
53378                         blue: Math.round((end.blue - start.blue) * damper)
53379                     }
53380                 };
53381             },
53382
53383             get: function(start, end, damper, initialFrom) {
53384                 var ln = start.length,
53385                     out = [],
53386                     i, initial;
53387                 for (i = 0; i < ln; i++) {
53388                     if (initialFrom) {
53389                         initial = initialFrom[i][1].from;
53390                     }
53391                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
53392                 }
53393                 return out;
53394             },
53395
53396             set: function(values, easing) {
53397                 var ln = values.length,
53398                     out = [],
53399                     i, val, parsedString, from, delta;
53400                 for (i = 0; i < ln; i++) {
53401                     val = values[i][1];
53402                     if (val) {
53403                         from = val.from;
53404                         delta = val.delta;
53405                         //multiple checks to reformat the color if it can't recognized by computeDelta.
53406                         val = (typeof val == 'object' && 'red' in val)? 
53407                                 'rgb(' + val.red + ', ' + val.green + ', ' + val.blue + ')' : val;
53408                         val = (typeof val == 'object' && val.length)? val[0] : val;
53409                         if (typeof val == 'undefined') {
53410                             return [];
53411                         }
53412                         parsedString = typeof val == 'string'? val :
53413                             'rgb(' + [
53414                                   (from.red + Math.round(delta.red * easing)) % 256,
53415                                   (from.green + Math.round(delta.green * easing)) % 256,
53416                                   (from.blue + Math.round(delta.blue * easing)) % 256
53417                               ].join(',') + ')';
53418                         out.push([
53419                             values[i][0],
53420                             parsedString
53421                         ]);
53422                     }
53423                 }
53424                 return out;
53425             }
53426         },
53427         object: {
53428             interpolate: function(prop, damper) {
53429                 damper = (typeof damper == 'number') ? damper : 1;
53430                 var out = {},
53431                     p;
53432                 for(p in prop) {
53433                     out[p] = parseInt(prop[p], 10) * damper;
53434                 }
53435                 return out;
53436             },
53437
53438             computeDelta: function(from, end, damper, initial) {
53439                 from = this.interpolate(from);
53440                 end = this.interpolate(end, damper);
53441                 var start = initial ? initial : from,
53442                     delta = {},
53443                     p;
53444
53445                 for(p in end) {
53446                     delta[p] = end[p] - start[p];
53447                 }
53448                 return {
53449                     from:  from,
53450                     delta: delta
53451                 };
53452             },
53453
53454             get: function(start, end, damper, initialFrom) {
53455                 var ln = start.length,
53456                     out = [],
53457                     i, initial;
53458                 for (i = 0; i < ln; i++) {
53459                     if (initialFrom) {
53460                         initial = initialFrom[i][1].from;
53461                     }
53462                     out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
53463                 }
53464                 return out;
53465             },
53466
53467             set: function(values, easing) {
53468                 var ln = values.length,
53469                     out = [],
53470                     outObject = {},
53471                     i, from, delta, val, p;
53472                 for (i = 0; i < ln; i++) {
53473                     val  = values[i][1];
53474                     from = val.from;
53475                     delta = val.delta;
53476                     for (p in from) {
53477                         outObject[p] = Math.round(from[p] + delta[p] * easing);
53478                     }
53479                     out.push([
53480                         values[i][0],
53481                         outObject
53482                     ]);
53483                 }
53484                 return out;
53485             }
53486         },
53487
53488         path: {
53489             computeDelta: function(from, end, damper, initial) {
53490                 damper = (typeof damper == 'number') ? damper : 1;
53491                 var start;
53492                 from = +from || 0;
53493                 end = +end || 0;
53494                 start = (initial != null) ? initial : from;
53495                 return {
53496                     from: from,
53497                     delta: (end - start) * damper
53498                 };
53499             },
53500
53501             forcePath: function(path) {
53502                 if (!Ext.isArray(path) && !Ext.isArray(path[0])) {
53503                     path = Ext.draw.Draw.parsePathString(path);
53504                 }
53505                 return path;
53506             },
53507
53508             get: function(start, end, damper, initialFrom) {
53509                 var endPath = this.forcePath(end),
53510                     out = [],
53511                     startLn = start.length,
53512                     startPathLn, pointsLn, i, deltaPath, initial, j, k, path, startPath;
53513                 for (i = 0; i < startLn; i++) {
53514                     startPath = this.forcePath(start[i][1]);
53515
53516                     deltaPath = Ext.draw.Draw.interpolatePaths(startPath, endPath);
53517                     startPath = deltaPath[0];
53518                     endPath = deltaPath[1];
53519
53520                     startPathLn = startPath.length;
53521                     path = [];
53522                     for (j = 0; j < startPathLn; j++) {
53523                         deltaPath = [startPath[j][0]];
53524                         pointsLn = startPath[j].length;
53525                         for (k = 1; k < pointsLn; k++) {
53526                             initial = initialFrom && initialFrom[0][1][j][k].from;
53527                             deltaPath.push(this.computeDelta(startPath[j][k], endPath[j][k], damper, initial));
53528                         }
53529                         path.push(deltaPath);
53530                     }
53531                     out.push([start[i][0], path]);
53532                 }
53533                 return out;
53534             },
53535
53536             set: function(values, easing) {
53537                 var ln = values.length,
53538                     out = [],
53539                     i, j, k, newPath, calcPath, deltaPath, deltaPathLn, pointsLn;
53540                 for (i = 0; i < ln; i++) {
53541                     deltaPath = values[i][1];
53542                     newPath = [];
53543                     deltaPathLn = deltaPath.length;
53544                     for (j = 0; j < deltaPathLn; j++) {
53545                         calcPath = [deltaPath[j][0]];
53546                         pointsLn = deltaPath[j].length;
53547                         for (k = 1; k < pointsLn; k++) {
53548                             calcPath.push(deltaPath[j][k].from + deltaPath[j][k].delta * easing);
53549                         }
53550                         newPath.push(calcPath.join(','));
53551                     }
53552                     out.push([values[i][0], newPath.join(',')]);
53553                 }
53554                 return out;
53555             }
53556         }
53557         /* End Definitions */
53558     }
53559 }, function() {
53560     Ext.each([
53561         'outlineColor',
53562         'backgroundColor',
53563         'borderColor',
53564         'borderTopColor',
53565         'borderRightColor', 
53566         'borderBottomColor', 
53567         'borderLeftColor',
53568         'fill',
53569         'stroke'
53570     ], function(prop) {
53571         this[prop] = this.color;
53572     }, this);
53573 });
53574 /**
53575  * @class Ext.fx.Anim
53576  *
53577  * This class manages animation for a specific {@link #target}. The animation allows
53578  * animation of various properties on the target, such as size, position, color and others.
53579  *
53580  * ## Starting Conditions
53581  * The starting conditions for the animation are provided by the {@link #from} configuration.
53582  * Any/all of the properties in the {@link #from} configuration can be specified. If a particular
53583  * property is not defined, the starting value for that property will be read directly from the target.
53584  *
53585  * ## End Conditions
53586  * The ending conditions for the animation are provided by the {@link #to} configuration. These mark
53587  * the final values once the animations has finished. The values in the {@link #from} can mirror
53588  * those in the {@link #to} configuration to provide a starting point.
53589  *
53590  * ## Other Options
53591  *  - {@link #duration}: Specifies the time period of the animation.
53592  *  - {@link #easing}: Specifies the easing of the animation.
53593  *  - {@link #iterations}: Allows the animation to repeat a number of times.
53594  *  - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
53595  *
53596  * ## Example Code
53597  *
53598  *     @example
53599  *     var myComponent = Ext.create('Ext.Component', {
53600  *         renderTo: document.body,
53601  *         width: 200,
53602  *         height: 200,
53603  *         style: 'border: 1px solid red;'
53604  *     });
53605  *
53606  *     Ext.create('Ext.fx.Anim', {
53607  *         target: myComponent,
53608  *         duration: 1000,
53609  *         from: {
53610  *             width: 400 //starting width 400
53611  *         },
53612  *         to: {
53613  *             width: 300, //end width 300
53614  *             height: 300 // end width 300
53615  *         }
53616  *     });
53617  */
53618 Ext.define('Ext.fx.Anim', {
53619
53620     /* Begin Definitions */
53621
53622     mixins: {
53623         observable: 'Ext.util.Observable'
53624     },
53625
53626     requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'],
53627
53628     /* End Definitions */
53629
53630     isAnimation: true,
53631
53632     /**
53633      * @cfg {Function} callback
53634      * A function to be run after the animation has completed.
53635      */
53636
53637     /**
53638      * @cfg {Function} scope
53639      * The scope that the {@link #callback} function will be called with
53640      */
53641
53642     /**
53643      * @cfg {Number} duration
53644      * Time in milliseconds for a single animation to last. Defaults to 250. If the {@link #iterations} property is
53645      * specified, then each animate will take the same duration for each iteration.
53646      */
53647     duration: 250,
53648
53649     /**
53650      * @cfg {Number} delay
53651      * Time to delay before starting the animation. Defaults to 0.
53652      */
53653     delay: 0,
53654
53655     /* private used to track a delayed starting time */
53656     delayStart: 0,
53657
53658     /**
53659      * @cfg {Boolean} dynamic
53660      * 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.
53661      */
53662     dynamic: false,
53663
53664     /**
53665      * @cfg {String} easing
53666 This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
53667 speed over its duration.
53668
53669          -backIn
53670          -backOut
53671          -bounceIn
53672          -bounceOut
53673          -ease
53674          -easeIn
53675          -easeOut
53676          -easeInOut
53677          -elasticIn
53678          -elasticOut
53679          -cubic-bezier(x1, y1, x2, y2)
53680
53681 Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
53682 specification.  The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
53683 be in the range [0, 1] or the definition is invalid.
53684
53685 [0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
53686
53687      * @markdown
53688      */
53689     easing: 'ease',
53690
53691      /**
53692       * @cfg {Object} keyframes
53693       * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
53694       * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
53695       * "from" or "to"</b>.  A keyframe declaration without these keyframe selectors is invalid and will not be available for
53696       * animation.  The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
53697       * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
53698  <pre><code>
53699 keyframes : {
53700     '0%': {
53701         left: 100
53702     },
53703     '40%': {
53704         left: 150
53705     },
53706     '60%': {
53707         left: 75
53708     },
53709     '100%': {
53710         left: 100
53711     }
53712 }
53713  </code></pre>
53714       */
53715
53716     /**
53717      * @private
53718      */
53719     damper: 1,
53720
53721     /**
53722      * @private
53723      */
53724     bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
53725
53726     /**
53727      * Run the animation from the end to the beginning
53728      * Defaults to false.
53729      * @cfg {Boolean} reverse
53730      */
53731     reverse: false,
53732
53733     /**
53734      * Flag to determine if the animation has started
53735      * @property running
53736      * @type Boolean
53737      */
53738     running: false,
53739
53740     /**
53741      * Flag to determine if the animation is paused. Only set this to true if you need to
53742      * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
53743      * @property paused
53744      * @type Boolean
53745      */
53746     paused: false,
53747
53748     /**
53749      * Number of times to execute the animation. Defaults to 1.
53750      * @cfg {Number} iterations
53751      */
53752     iterations: 1,
53753
53754     /**
53755      * Used in conjunction with iterations to reverse the animation each time an iteration completes.
53756      * @cfg {Boolean} alternate
53757      * Defaults to false.
53758      */
53759     alternate: false,
53760
53761     /**
53762      * Current iteration the animation is running.
53763      * @property currentIteration
53764      * @type Number
53765      */
53766     currentIteration: 0,
53767
53768     /**
53769      * Starting time of the animation.
53770      * @property startTime
53771      * @type Date
53772      */
53773     startTime: 0,
53774
53775     /**
53776      * Contains a cache of the interpolators to be used.
53777      * @private
53778      * @property propHandlers
53779      * @type Object
53780      */
53781
53782     /**
53783      * @cfg {String/Object} target
53784      * The {@link Ext.fx.target.Target} to apply the animation to.  This should only be specified when creating an Ext.fx.Anim directly.
53785      * The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can
53786      * pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object
53787      * automatically.
53788      */
53789
53790     /**
53791      * @cfg {Object} from
53792      * An object containing property/value pairs for the beginning of the animation.  If not specified, the current state of the
53793      * Ext.fx.target will be used. For example:
53794 <pre><code>
53795 from : {
53796     opacity: 0,       // Transparent
53797     color: '#ffffff', // White
53798     left: 0
53799 }
53800 </code></pre>
53801      */
53802
53803     /**
53804      * @cfg {Object} to
53805      * An object containing property/value pairs for the end of the animation. For example:
53806  <pre><code>
53807  to : {
53808      opacity: 1,       // Opaque
53809      color: '#00ff00', // Green
53810      left: 500
53811  }
53812  </code></pre>
53813      */
53814
53815     // @private
53816     constructor: function(config) {
53817         var me = this,
53818             curve;
53819             
53820         config = config || {};
53821         // If keyframes are passed, they really want an Animator instead.
53822         if (config.keyframes) {
53823             return Ext.create('Ext.fx.Animator', config);
53824         }
53825         config = Ext.apply(me, config);
53826         if (me.from === undefined) {
53827             me.from = {};
53828         }
53829         me.propHandlers = {};
53830         me.config = config;
53831         me.target = Ext.fx.Manager.createTarget(me.target);
53832         me.easingFn = Ext.fx.Easing[me.easing];
53833         me.target.dynamic = me.dynamic;
53834
53835         // If not a pre-defined curve, try a cubic-bezier
53836         if (!me.easingFn) {
53837             me.easingFn = String(me.easing).match(me.bezierRE);
53838             if (me.easingFn && me.easingFn.length == 5) {
53839                 curve = me.easingFn;
53840                 me.easingFn = Ext.fx.CubicBezier.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
53841             }
53842         }
53843         me.id = Ext.id(null, 'ext-anim-');
53844         Ext.fx.Manager.addAnim(me);
53845         me.addEvents(
53846             /**
53847              * @event beforeanimate
53848              * Fires before the animation starts. A handler can return false to cancel the animation.
53849              * @param {Ext.fx.Anim} this
53850              */
53851             'beforeanimate',
53852              /**
53853               * @event afteranimate
53854               * Fires when the animation is complete.
53855               * @param {Ext.fx.Anim} this
53856               * @param {Date} startTime
53857               */
53858             'afteranimate',
53859              /**
53860               * @event lastframe
53861               * Fires when the animation's last frame has been set.
53862               * @param {Ext.fx.Anim} this
53863               * @param {Date} startTime
53864               */
53865             'lastframe'
53866         );
53867         me.mixins.observable.constructor.call(me, config);
53868         if (config.callback) {
53869             me.on('afteranimate', config.callback, config.scope);
53870         }
53871         return me;
53872     },
53873
53874     /**
53875      * @private
53876      * Helper to the target
53877      */
53878     setAttr: function(attr, value) {
53879         return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
53880     },
53881
53882     /**
53883      * @private
53884      * Set up the initial currentAttrs hash.
53885      */
53886     initAttrs: function() {
53887         var me = this,
53888             from = me.from,
53889             to = me.to,
53890             initialFrom = me.initialFrom || {},
53891             out = {},
53892             start, end, propHandler, attr;
53893
53894         for (attr in to) {
53895             if (to.hasOwnProperty(attr)) {
53896                 start = me.target.getAttr(attr, from[attr]);
53897                 end = to[attr];
53898                 // Use default (numeric) property handler
53899                 if (!Ext.fx.PropertyHandler[attr]) {
53900                     if (Ext.isObject(end)) {
53901                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object;
53902                     } else {
53903                         propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler;
53904                     }
53905                 }
53906                 // Use custom handler
53907                 else {
53908                     propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr];
53909                 }
53910                 out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr);
53911             }
53912         }
53913         me.currentAttrs = out;
53914     },
53915
53916     /**
53917      * @private
53918      * Fires beforeanimate and sets the running flag.
53919      */
53920     start: function(startTime) {
53921         var me = this,
53922             delay = me.delay,
53923             delayStart = me.delayStart,
53924             delayDelta;
53925         if (delay) {
53926             if (!delayStart) {
53927                 me.delayStart = startTime;
53928                 return;
53929             }
53930             else {
53931                 delayDelta = startTime - delayStart;
53932                 if (delayDelta < delay) {
53933                     return;
53934                 }
53935                 else {
53936                     // Compensate for frame delay;
53937                     startTime = new Date(delayStart.getTime() + delay);
53938                 }
53939             }
53940         }
53941         if (me.fireEvent('beforeanimate', me) !== false) {
53942             me.startTime = startTime;
53943             if (!me.paused && !me.currentAttrs) {
53944                 me.initAttrs();
53945             }
53946             me.running = true;
53947         }
53948     },
53949
53950     /**
53951      * @private
53952      * Calculate attribute value at the passed timestamp.
53953      * @returns a hash of the new attributes.
53954      */
53955     runAnim: function(elapsedTime) {
53956         var me = this,
53957             attrs = me.currentAttrs,
53958             duration = me.duration,
53959             easingFn = me.easingFn,
53960             propHandlers = me.propHandlers,
53961             ret = {},
53962             easing, values, attr, lastFrame;
53963
53964         if (elapsedTime >= duration) {
53965             elapsedTime = duration;
53966             lastFrame = true;
53967         }
53968         if (me.reverse) {
53969             elapsedTime = duration - elapsedTime;
53970         }
53971
53972         for (attr in attrs) {
53973             if (attrs.hasOwnProperty(attr)) {
53974                 values = attrs[attr];
53975                 easing = lastFrame ? 1 : easingFn(elapsedTime / duration);
53976                 ret[attr] = propHandlers[attr].set(values, easing);
53977             }
53978         }
53979         return ret;
53980     },
53981
53982     /**
53983      * @private
53984      * Perform lastFrame cleanup and handle iterations
53985      * @returns a hash of the new attributes.
53986      */
53987     lastFrame: function() {
53988         var me = this,
53989             iter = me.iterations,
53990             iterCount = me.currentIteration;
53991
53992         iterCount++;
53993         if (iterCount < iter) {
53994             if (me.alternate) {
53995                 me.reverse = !me.reverse;
53996             }
53997             me.startTime = new Date();
53998             me.currentIteration = iterCount;
53999             // Turn off paused for CSS3 Transitions
54000             me.paused = false;
54001         }
54002         else {
54003             me.currentIteration = 0;
54004             me.end();
54005             me.fireEvent('lastframe', me, me.startTime);
54006         }
54007     },
54008
54009     /**
54010      * Fire afteranimate event and end the animation. Usually called automatically when the
54011      * animation reaches its final frame, but can also be called manually to pre-emptively
54012      * stop and destroy the running animation.
54013      */
54014     end: function() {
54015         var me = this;
54016         me.startTime = 0;
54017         me.paused = false;
54018         me.running = false;
54019         Ext.fx.Manager.removeAnim(me);
54020         me.fireEvent('afteranimate', me, me.startTime);
54021     }
54022 });
54023 // Set flag to indicate that Fx is available. Class might not be available immediately.
54024 Ext.enableFx = true;
54025
54026 /*
54027  * This is a derivative of the similarly named class in the YUI Library.
54028  * The original license:
54029  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
54030  * Code licensed under the BSD License:
54031  * http://developer.yahoo.net/yui/license.txt
54032  */
54033
54034
54035 /**
54036  * Defines the interface and base operation of items that that can be
54037  * dragged or can be drop targets.  It was designed to be extended, overriding
54038  * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
54039  * Up to three html elements can be associated with a DragDrop instance:
54040  *
54041  * - linked element: the element that is passed into the constructor.
54042  *   This is the element which defines the boundaries for interaction with
54043  *   other DragDrop objects.
54044  *
54045  * - handle element(s): The drag operation only occurs if the element that
54046  *   was clicked matches a handle element.  By default this is the linked
54047  *   element, but there are times that you will want only a portion of the
54048  *   linked element to initiate the drag operation, and the setHandleElId()
54049  *   method provides a way to define this.
54050  *
54051  * - drag element: this represents the element that would be moved along
54052  *   with the cursor during a drag operation.  By default, this is the linked
54053  *   element itself as in {@link Ext.dd.DD}.  setDragElId() lets you define
54054  *   a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
54055  *
54056  * This class should not be instantiated until the onload event to ensure that
54057  * the associated elements are available.
54058  * The following would define a DragDrop obj that would interact with any
54059  * other DragDrop obj in the "group1" group:
54060  *
54061  *     dd = new Ext.dd.DragDrop("div1", "group1");
54062  *
54063  * Since none of the event handlers have been implemented, nothing would
54064  * actually happen if you were to run the code above.  Normally you would
54065  * override this class or one of the default implementations, but you can
54066  * also override the methods you want on an instance of the class...
54067  *
54068  *     dd.onDragDrop = function(e, id) {
54069  *         alert("dd was dropped on " + id);
54070  *     }
54071  *
54072  */
54073 Ext.define('Ext.dd.DragDrop', {
54074     requires: ['Ext.dd.DragDropManager'],
54075
54076     /**
54077      * Creates new DragDrop.
54078      * @param {String} id of the element that is linked to this instance
54079      * @param {String} sGroup the group of related DragDrop objects
54080      * @param {Object} config an object containing configurable attributes.
54081      * Valid properties for DragDrop:
54082      *
54083      * - padding
54084      * - isTarget
54085      * - maintainOffset
54086      * - primaryButtonOnly
54087      */
54088     constructor: function(id, sGroup, config) {
54089         if(id) {
54090             this.init(id, sGroup, config);
54091         }
54092     },
54093
54094     /**
54095      * Set to false to enable a DragDrop object to fire drag events while dragging
54096      * over its own Element. Defaults to true - DragDrop objects do not by default
54097      * fire drag events to themselves.
54098      * @property ignoreSelf
54099      * @type Boolean
54100      */
54101
54102     /**
54103      * The id of the element associated with this object.  This is what we
54104      * refer to as the "linked element" because the size and position of
54105      * this element is used to determine when the drag and drop objects have
54106      * interacted.
54107      * @property id
54108      * @type String
54109      */
54110     id: null,
54111
54112     /**
54113      * Configuration attributes passed into the constructor
54114      * @property config
54115      * @type Object
54116      */
54117     config: null,
54118
54119     /**
54120      * The id of the element that will be dragged.  By default this is same
54121      * as the linked element, but could be changed to another element. Ex:
54122      * Ext.dd.DDProxy
54123      * @property dragElId
54124      * @type String
54125      * @private
54126      */
54127     dragElId: null,
54128
54129     /**
54130      * The ID of the element that initiates the drag operation.  By default
54131      * this is the linked element, but could be changed to be a child of this
54132      * element.  This lets us do things like only starting the drag when the
54133      * header element within the linked html element is clicked.
54134      * @property handleElId
54135      * @type String
54136      * @private
54137      */
54138     handleElId: null,
54139
54140     /**
54141      * An object who's property names identify HTML tags to be considered invalid as drag handles.
54142      * A non-null property value identifies the tag as invalid. Defaults to the
54143      * following value which prevents drag operations from being initiated by &lt;a> elements:<pre><code>
54144 {
54145     A: "A"
54146 }</code></pre>
54147      * @property invalidHandleTypes
54148      * @type Object
54149      */
54150     invalidHandleTypes: null,
54151
54152     /**
54153      * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
54154      * A non-null property value identifies the ID as invalid. For example, to prevent
54155      * dragging from being initiated on element ID "foo", use:<pre><code>
54156 {
54157     foo: true
54158 }</code></pre>
54159      * @property invalidHandleIds
54160      * @type Object
54161      */
54162     invalidHandleIds: null,
54163
54164     /**
54165      * An Array of CSS class names for elements to be considered in valid as drag handles.
54166      * @property {String[]} invalidHandleClasses
54167      */
54168     invalidHandleClasses: null,
54169
54170     /**
54171      * The linked element's absolute X position at the time the drag was
54172      * started
54173      * @property startPageX
54174      * @type Number
54175      * @private
54176      */
54177     startPageX: 0,
54178
54179     /**
54180      * The linked element's absolute X position at the time the drag was
54181      * started
54182      * @property startPageY
54183      * @type Number
54184      * @private
54185      */
54186     startPageY: 0,
54187
54188     /**
54189      * The group defines a logical collection of DragDrop objects that are
54190      * related.  Instances only get events when interacting with other
54191      * DragDrop object in the same group.  This lets us define multiple
54192      * groups using a single DragDrop subclass if we want.
54193      * @property groups
54194      * @type Object An object in the format {'group1':true, 'group2':true}
54195      */
54196     groups: null,
54197
54198     /**
54199      * Individual drag/drop instances can be locked.  This will prevent
54200      * onmousedown start drag.
54201      * @property locked
54202      * @type Boolean
54203      * @private
54204      */
54205     locked: false,
54206
54207     /**
54208      * Locks this instance
54209      */
54210     lock: function() {
54211         this.locked = true;
54212     },
54213
54214     /**
54215      * When set to true, other DD objects in cooperating DDGroups do not receive
54216      * notification events when this DD object is dragged over them. Defaults to false.
54217      * @property moveOnly
54218      * @type Boolean
54219      */
54220     moveOnly: false,
54221
54222     /**
54223      * Unlocks this instace
54224      */
54225     unlock: function() {
54226         this.locked = false;
54227     },
54228
54229     /**
54230      * By default, all instances can be a drop target.  This can be disabled by
54231      * setting isTarget to false.
54232      * @property isTarget
54233      * @type Boolean
54234      */
54235     isTarget: true,
54236
54237     /**
54238      * The padding configured for this drag and drop object for calculating
54239      * the drop zone intersection with this object.
54240      * An array containing the 4 padding values: [top, right, bottom, left]
54241      * @property {Number[]} padding
54242      */
54243     padding: null,
54244
54245     /**
54246      * Cached reference to the linked element
54247      * @property _domRef
54248      * @private
54249      */
54250     _domRef: null,
54251
54252     /**
54253      * Internal typeof flag
54254      * @property __ygDragDrop
54255      * @private
54256      */
54257     __ygDragDrop: true,
54258
54259     /**
54260      * Set to true when horizontal contraints are applied
54261      * @property constrainX
54262      * @type Boolean
54263      * @private
54264      */
54265     constrainX: false,
54266
54267     /**
54268      * Set to true when vertical contraints are applied
54269      * @property constrainY
54270      * @type Boolean
54271      * @private
54272      */
54273     constrainY: false,
54274
54275     /**
54276      * The left constraint
54277      * @property minX
54278      * @type Number
54279      * @private
54280      */
54281     minX: 0,
54282
54283     /**
54284      * The right constraint
54285      * @property maxX
54286      * @type Number
54287      * @private
54288      */
54289     maxX: 0,
54290
54291     /**
54292      * The up constraint
54293      * @property minY
54294      * @type Number
54295      * @private
54296      */
54297     minY: 0,
54298
54299     /**
54300      * The down constraint
54301      * @property maxY
54302      * @type Number
54303      * @private
54304      */
54305     maxY: 0,
54306
54307     /**
54308      * Maintain offsets when we resetconstraints.  Set to true when you want
54309      * the position of the element relative to its parent to stay the same
54310      * when the page changes
54311      *
54312      * @property maintainOffset
54313      * @type Boolean
54314      */
54315     maintainOffset: false,
54316
54317     /**
54318      * Array of pixel locations the element will snap to if we specified a
54319      * horizontal graduation/interval.  This array is generated automatically
54320      * when you define a tick interval.
54321      * @property {Number[]} xTicks
54322      */
54323     xTicks: null,
54324
54325     /**
54326      * Array of pixel locations the element will snap to if we specified a
54327      * vertical graduation/interval.  This array is generated automatically
54328      * when you define a tick interval.
54329      * @property {Number[]} yTicks
54330      */
54331     yTicks: null,
54332
54333     /**
54334      * By default the drag and drop instance will only respond to the primary
54335      * button click (left button for a right-handed mouse).  Set to true to
54336      * allow drag and drop to start with any mouse click that is propogated
54337      * by the browser
54338      * @property primaryButtonOnly
54339      * @type Boolean
54340      */
54341     primaryButtonOnly: true,
54342
54343     /**
54344      * The available property is false until the linked dom element is accessible.
54345      * @property available
54346      * @type Boolean
54347      */
54348     available: false,
54349
54350     /**
54351      * By default, drags can only be initiated if the mousedown occurs in the
54352      * region the linked element is.  This is done in part to work around a
54353      * bug in some browsers that mis-report the mousedown if the previous
54354      * mouseup happened outside of the window.  This property is set to true
54355      * if outer handles are defined. Defaults to false.
54356      *
54357      * @property hasOuterHandles
54358      * @type Boolean
54359      */
54360     hasOuterHandles: false,
54361
54362     /**
54363      * Code that executes immediately before the startDrag event
54364      * @private
54365      */
54366     b4StartDrag: function(x, y) { },
54367
54368     /**
54369      * Abstract method called after a drag/drop object is clicked
54370      * and the drag or mousedown time thresholds have beeen met.
54371      * @param {Number} X click location
54372      * @param {Number} Y click location
54373      */
54374     startDrag: function(x, y) { /* override this */ },
54375
54376     /**
54377      * Code that executes immediately before the onDrag event
54378      * @private
54379      */
54380     b4Drag: function(e) { },
54381
54382     /**
54383      * Abstract method called during the onMouseMove event while dragging an
54384      * object.
54385      * @param {Event} e the mousemove event
54386      */
54387     onDrag: function(e) { /* override this */ },
54388
54389     /**
54390      * Abstract method called when this element fist begins hovering over
54391      * another DragDrop obj
54392      * @param {Event} e the mousemove event
54393      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
54394      * id this is hovering over.  In INTERSECT mode, an array of one or more
54395      * dragdrop items being hovered over.
54396      */
54397     onDragEnter: function(e, id) { /* override this */ },
54398
54399     /**
54400      * Code that executes immediately before the onDragOver event
54401      * @private
54402      */
54403     b4DragOver: function(e) { },
54404
54405     /**
54406      * Abstract method called when this element is hovering over another
54407      * DragDrop obj
54408      * @param {Event} e the mousemove event
54409      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
54410      * id this is hovering over.  In INTERSECT mode, an array of dd items
54411      * being hovered over.
54412      */
54413     onDragOver: function(e, id) { /* override this */ },
54414
54415     /**
54416      * Code that executes immediately before the onDragOut event
54417      * @private
54418      */
54419     b4DragOut: function(e) { },
54420
54421     /**
54422      * Abstract method called when we are no longer hovering over an element
54423      * @param {Event} e the mousemove event
54424      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
54425      * id this was hovering over.  In INTERSECT mode, an array of dd items
54426      * that the mouse is no longer over.
54427      */
54428     onDragOut: function(e, id) { /* override this */ },
54429
54430     /**
54431      * Code that executes immediately before the onDragDrop event
54432      * @private
54433      */
54434     b4DragDrop: function(e) { },
54435
54436     /**
54437      * Abstract method called when this item is dropped on another DragDrop
54438      * obj
54439      * @param {Event} e the mouseup event
54440      * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
54441      * id this was dropped on.  In INTERSECT mode, an array of dd items this
54442      * was dropped on.
54443      */
54444     onDragDrop: function(e, id) { /* override this */ },
54445
54446     /**
54447      * Abstract method called when this item is dropped on an area with no
54448      * drop target
54449      * @param {Event} e the mouseup event
54450      */
54451     onInvalidDrop: function(e) { /* override this */ },
54452
54453     /**
54454      * Code that executes immediately before the endDrag event
54455      * @private
54456      */
54457     b4EndDrag: function(e) { },
54458
54459     /**
54460      * Called when we are done dragging the object
54461      * @param {Event} e the mouseup event
54462      */
54463     endDrag: function(e) { /* override this */ },
54464
54465     /**
54466      * Code executed immediately before the onMouseDown event
54467      * @param {Event} e the mousedown event
54468      * @private
54469      */
54470     b4MouseDown: function(e) {  },
54471
54472     /**
54473      * Called when a drag/drop obj gets a mousedown
54474      * @param {Event} e the mousedown event
54475      */
54476     onMouseDown: function(e) { /* override this */ },
54477
54478     /**
54479      * Called when a drag/drop obj gets a mouseup
54480      * @param {Event} e the mouseup event
54481      */
54482     onMouseUp: function(e) { /* override this */ },
54483
54484     /**
54485      * Override the onAvailable method to do what is needed after the initial
54486      * position was determined.
54487      */
54488     onAvailable: function () {
54489     },
54490
54491     /**
54492      * @property {Object} defaultPadding
54493      * Provides default constraint padding to "constrainTo" elements.
54494      */
54495     defaultPadding: {
54496         left: 0,
54497         right: 0,
54498         top: 0,
54499         bottom: 0
54500     },
54501
54502     /**
54503      * Initializes the drag drop object's constraints to restrict movement to a certain element.
54504      *
54505      * Usage:
54506      *
54507      *     var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
54508      *                    { dragElId: "existingProxyDiv" });
54509      *     dd.startDrag = function(){
54510      *         this.constrainTo("parent-id");
54511      *     };
54512      *
54513      * Or you can initalize it using the {@link Ext.Element} object:
54514      *
54515      *     Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
54516      *         startDrag : function(){
54517      *             this.constrainTo("parent-id");
54518      *         }
54519      *     });
54520      *
54521      * @param {String/HTMLElement/Ext.Element} constrainTo The element or element ID to constrain to.
54522      * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
54523      * and can be either a number for symmetrical padding (4 would be equal to `{left:4, right:4, top:4, bottom:4}`) or
54524      * an object containing the sides to pad. For example: `{right:10, bottom:10}`
54525      * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
54526      */
54527     constrainTo : function(constrainTo, pad, inContent){
54528         if(Ext.isNumber(pad)){
54529             pad = {left: pad, right:pad, top:pad, bottom:pad};
54530         }
54531         pad = pad || this.defaultPadding;
54532         var b = Ext.get(this.getEl()).getBox(),
54533             ce = Ext.get(constrainTo),
54534             s = ce.getScroll(),
54535             c,
54536             cd = ce.dom;
54537         if(cd == document.body){
54538             c = { x: s.left, y: s.top, width: Ext.Element.getViewWidth(), height: Ext.Element.getViewHeight()};
54539         }else{
54540             var xy = ce.getXY();
54541             c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
54542         }
54543
54544
54545         var topSpace = b.y - c.y,
54546             leftSpace = b.x - c.x;
54547
54548         this.resetConstraints();
54549         this.setXConstraint(leftSpace - (pad.left||0), // left
54550                 c.width - leftSpace - b.width - (pad.right||0), //right
54551                                 this.xTickSize
54552         );
54553         this.setYConstraint(topSpace - (pad.top||0), //top
54554                 c.height - topSpace - b.height - (pad.bottom||0), //bottom
54555                                 this.yTickSize
54556         );
54557     },
54558
54559     /**
54560      * Returns a reference to the linked element
54561      * @return {HTMLElement} the html element
54562      */
54563     getEl: function() {
54564         if (!this._domRef) {
54565             this._domRef = Ext.getDom(this.id);
54566         }
54567
54568         return this._domRef;
54569     },
54570
54571     /**
54572      * Returns a reference to the actual element to drag.  By default this is
54573      * the same as the html element, but it can be assigned to another
54574      * element. An example of this can be found in Ext.dd.DDProxy
54575      * @return {HTMLElement} the html element
54576      */
54577     getDragEl: function() {
54578         return Ext.getDom(this.dragElId);
54579     },
54580
54581     /**
54582      * Sets up the DragDrop object.  Must be called in the constructor of any
54583      * Ext.dd.DragDrop subclass
54584      * @param {String} id the id of the linked element
54585      * @param {String} sGroup the group of related items
54586      * @param {Object} config configuration attributes
54587      */
54588     init: function(id, sGroup, config) {
54589         this.initTarget(id, sGroup, config);
54590         Ext.EventManager.on(this.id, "mousedown", this.handleMouseDown, this);
54591         // Ext.EventManager.on(this.id, "selectstart", Event.preventDefault);
54592     },
54593
54594     /**
54595      * Initializes Targeting functionality only... the object does not
54596      * get a mousedown handler.
54597      * @param {String} id the id of the linked element
54598      * @param {String} sGroup the group of related items
54599      * @param {Object} config configuration attributes
54600      */
54601     initTarget: function(id, sGroup, config) {
54602         // configuration attributes
54603         this.config = config || {};
54604
54605         // create a local reference to the drag and drop manager
54606         this.DDMInstance = Ext.dd.DragDropManager;
54607         // initialize the groups array
54608         this.groups = {};
54609
54610         // assume that we have an element reference instead of an id if the
54611         // parameter is not a string
54612         if (typeof id !== "string") {
54613             id = Ext.id(id);
54614         }
54615
54616         // set the id
54617         this.id = id;
54618
54619         // add to an interaction group
54620         this.addToGroup((sGroup) ? sGroup : "default");
54621
54622         // We don't want to register this as the handle with the manager
54623         // so we just set the id rather than calling the setter.
54624         this.handleElId = id;
54625
54626         // the linked element is the element that gets dragged by default
54627         this.setDragElId(id);
54628
54629         // by default, clicked anchors will not start drag operations.
54630         this.invalidHandleTypes = { A: "A" };
54631         this.invalidHandleIds = {};
54632         this.invalidHandleClasses = [];
54633
54634         this.applyConfig();
54635
54636         this.handleOnAvailable();
54637     },
54638
54639     /**
54640      * Applies the configuration parameters that were passed into the constructor.
54641      * This is supposed to happen at each level through the inheritance chain.  So
54642      * a DDProxy implentation will execute apply config on DDProxy, DD, and
54643      * DragDrop in order to get all of the parameters that are available in
54644      * each object.
54645      */
54646     applyConfig: function() {
54647
54648         // configurable properties:
54649         //    padding, isTarget, maintainOffset, primaryButtonOnly
54650         this.padding           = this.config.padding || [0, 0, 0, 0];
54651         this.isTarget          = (this.config.isTarget !== false);
54652         this.maintainOffset    = (this.config.maintainOffset);
54653         this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
54654
54655     },
54656
54657     /**
54658      * Executed when the linked element is available
54659      * @private
54660      */
54661     handleOnAvailable: function() {
54662         this.available = true;
54663         this.resetConstraints();
54664         this.onAvailable();
54665     },
54666
54667     /**
54668      * Configures the padding for the target zone in px.  Effectively expands
54669      * (or reduces) the virtual object size for targeting calculations.
54670      * Supports css-style shorthand; if only one parameter is passed, all sides
54671      * will have that padding, and if only two are passed, the top and bottom
54672      * will have the first param, the left and right the second.
54673      * @param {Number} iTop    Top pad
54674      * @param {Number} iRight  Right pad
54675      * @param {Number} iBot    Bot pad
54676      * @param {Number} iLeft   Left pad
54677      */
54678     setPadding: function(iTop, iRight, iBot, iLeft) {
54679         // this.padding = [iLeft, iRight, iTop, iBot];
54680         if (!iRight && 0 !== iRight) {
54681             this.padding = [iTop, iTop, iTop, iTop];
54682         } else if (!iBot && 0 !== iBot) {
54683             this.padding = [iTop, iRight, iTop, iRight];
54684         } else {
54685             this.padding = [iTop, iRight, iBot, iLeft];
54686         }
54687     },
54688
54689     /**
54690      * Stores the initial placement of the linked element.
54691      * @param {Number} diffX   the X offset, default 0
54692      * @param {Number} diffY   the Y offset, default 0
54693      */
54694     setInitPosition: function(diffX, diffY) {
54695         var el = this.getEl();
54696
54697         if (!this.DDMInstance.verifyEl(el)) {
54698             return;
54699         }
54700
54701         var dx = diffX || 0;
54702         var dy = diffY || 0;
54703
54704         var p = Ext.Element.getXY( el );
54705
54706         this.initPageX = p[0] - dx;
54707         this.initPageY = p[1] - dy;
54708
54709         this.lastPageX = p[0];
54710         this.lastPageY = p[1];
54711
54712         this.setStartPosition(p);
54713     },
54714
54715     /**
54716      * Sets the start position of the element.  This is set when the obj
54717      * is initialized, the reset when a drag is started.
54718      * @param pos current position (from previous lookup)
54719      * @private
54720      */
54721     setStartPosition: function(pos) {
54722         var p = pos || Ext.Element.getXY( this.getEl() );
54723         this.deltaSetXY = null;
54724
54725         this.startPageX = p[0];
54726         this.startPageY = p[1];
54727     },
54728
54729     /**
54730      * Adds this instance to a group of related drag/drop objects.  All
54731      * instances belong to at least one group, and can belong to as many
54732      * groups as needed.
54733      * @param {String} sGroup the name of the group
54734      */
54735     addToGroup: function(sGroup) {
54736         this.groups[sGroup] = true;
54737         this.DDMInstance.regDragDrop(this, sGroup);
54738     },
54739
54740     /**
54741      * Removes this instance from the supplied interaction group
54742      * @param {String} sGroup  The group to drop
54743      */
54744     removeFromGroup: function(sGroup) {
54745         if (this.groups[sGroup]) {
54746             delete this.groups[sGroup];
54747         }
54748
54749         this.DDMInstance.removeDDFromGroup(this, sGroup);
54750     },
54751
54752     /**
54753      * Allows you to specify that an element other than the linked element
54754      * will be moved with the cursor during a drag
54755      * @param {String} id the id of the element that will be used to initiate the drag
54756      */
54757     setDragElId: function(id) {
54758         this.dragElId = id;
54759     },
54760
54761     /**
54762      * Allows you to specify a child of the linked element that should be
54763      * used to initiate the drag operation.  An example of this would be if
54764      * you have a content div with text and links.  Clicking anywhere in the
54765      * content area would normally start the drag operation.  Use this method
54766      * to specify that an element inside of the content div is the element
54767      * that starts the drag operation.
54768      * @param {String} id the id of the element that will be used to
54769      * initiate the drag.
54770      */
54771     setHandleElId: function(id) {
54772         if (typeof id !== "string") {
54773             id = Ext.id(id);
54774         }
54775         this.handleElId = id;
54776         this.DDMInstance.regHandle(this.id, id);
54777     },
54778
54779     /**
54780      * Allows you to set an element outside of the linked element as a drag
54781      * handle
54782      * @param {String} id the id of the element that will be used to initiate the drag
54783      */
54784     setOuterHandleElId: function(id) {
54785         if (typeof id !== "string") {
54786             id = Ext.id(id);
54787         }
54788         Ext.EventManager.on(id, "mousedown", this.handleMouseDown, this);
54789         this.setHandleElId(id);
54790
54791         this.hasOuterHandles = true;
54792     },
54793
54794     /**
54795      * Removes all drag and drop hooks for this element
54796      */
54797     unreg: function() {
54798         Ext.EventManager.un(this.id, "mousedown", this.handleMouseDown, this);
54799         this._domRef = null;
54800         this.DDMInstance._remove(this);
54801     },
54802
54803     destroy : function(){
54804         this.unreg();
54805     },
54806
54807     /**
54808      * Returns true if this instance is locked, or the drag drop mgr is locked
54809      * (meaning that all drag/drop is disabled on the page.)
54810      * @return {Boolean} true if this obj or all drag/drop is locked, else
54811      * false
54812      */
54813     isLocked: function() {
54814         return (this.DDMInstance.isLocked() || this.locked);
54815     },
54816
54817     /**
54818      * Called when this object is clicked
54819      * @param {Event} e
54820      * @param {Ext.dd.DragDrop} oDD the clicked dd object (this dd obj)
54821      * @private
54822      */
54823     handleMouseDown: function(e, oDD){
54824         if (this.primaryButtonOnly && e.button != 0) {
54825             return;
54826         }
54827
54828         if (this.isLocked()) {
54829             return;
54830         }
54831
54832         this.DDMInstance.refreshCache(this.groups);
54833
54834         var pt = e.getPoint();
54835         if (!this.hasOuterHandles && !this.DDMInstance.isOverTarget(pt, this) )  {
54836         } else {
54837             if (this.clickValidator(e)) {
54838                 // set the initial element position
54839                 this.setStartPosition();
54840                 this.b4MouseDown(e);
54841                 this.onMouseDown(e);
54842
54843                 this.DDMInstance.handleMouseDown(e, this);
54844
54845                 this.DDMInstance.stopEvent(e);
54846             } else {
54847
54848
54849             }
54850         }
54851     },
54852
54853     clickValidator: function(e) {
54854         var target = e.getTarget();
54855         return ( this.isValidHandleChild(target) &&
54856                     (this.id == this.handleElId ||
54857                         this.DDMInstance.handleWasClicked(target, this.id)) );
54858     },
54859
54860     /**
54861      * Allows you to specify a tag name that should not start a drag operation
54862      * when clicked.  This is designed to facilitate embedding links within a
54863      * drag handle that do something other than start the drag.
54864      * @method addInvalidHandleType
54865      * @param {String} tagName the type of element to exclude
54866      */
54867     addInvalidHandleType: function(tagName) {
54868         var type = tagName.toUpperCase();
54869         this.invalidHandleTypes[type] = type;
54870     },
54871
54872     /**
54873      * Lets you to specify an element id for a child of a drag handle
54874      * that should not initiate a drag
54875      * @method addInvalidHandleId
54876      * @param {String} id the element id of the element you wish to ignore
54877      */
54878     addInvalidHandleId: function(id) {
54879         if (typeof id !== "string") {
54880             id = Ext.id(id);
54881         }
54882         this.invalidHandleIds[id] = id;
54883     },
54884
54885     /**
54886      * Lets you specify a css class of elements that will not initiate a drag
54887      * @param {String} cssClass the class of the elements you wish to ignore
54888      */
54889     addInvalidHandleClass: function(cssClass) {
54890         this.invalidHandleClasses.push(cssClass);
54891     },
54892
54893     /**
54894      * Unsets an excluded tag name set by addInvalidHandleType
54895      * @param {String} tagName the type of element to unexclude
54896      */
54897     removeInvalidHandleType: function(tagName) {
54898         var type = tagName.toUpperCase();
54899         // this.invalidHandleTypes[type] = null;
54900         delete this.invalidHandleTypes[type];
54901     },
54902
54903     /**
54904      * Unsets an invalid handle id
54905      * @param {String} id the id of the element to re-enable
54906      */
54907     removeInvalidHandleId: function(id) {
54908         if (typeof id !== "string") {
54909             id = Ext.id(id);
54910         }
54911         delete this.invalidHandleIds[id];
54912     },
54913
54914     /**
54915      * Unsets an invalid css class
54916      * @param {String} cssClass the class of the element(s) you wish to
54917      * re-enable
54918      */
54919     removeInvalidHandleClass: function(cssClass) {
54920         for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
54921             if (this.invalidHandleClasses[i] == cssClass) {
54922                 delete this.invalidHandleClasses[i];
54923             }
54924         }
54925     },
54926
54927     /**
54928      * Checks the tag exclusion list to see if this click should be ignored
54929      * @param {HTMLElement} node the HTMLElement to evaluate
54930      * @return {Boolean} true if this is a valid tag type, false if not
54931      */
54932     isValidHandleChild: function(node) {
54933
54934         var valid = true;
54935         // var n = (node.nodeName == "#text") ? node.parentNode : node;
54936         var nodeName;
54937         try {
54938             nodeName = node.nodeName.toUpperCase();
54939         } catch(e) {
54940             nodeName = node.nodeName;
54941         }
54942         valid = valid && !this.invalidHandleTypes[nodeName];
54943         valid = valid && !this.invalidHandleIds[node.id];
54944
54945         for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
54946             valid = !Ext.fly(node).hasCls(this.invalidHandleClasses[i]);
54947         }
54948
54949
54950         return valid;
54951
54952     },
54953
54954     /**
54955      * Creates the array of horizontal tick marks if an interval was specified
54956      * in setXConstraint().
54957      * @private
54958      */
54959     setXTicks: function(iStartX, iTickSize) {
54960         this.xTicks = [];
54961         this.xTickSize = iTickSize;
54962
54963         var tickMap = {};
54964
54965         for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
54966             if (!tickMap[i]) {
54967                 this.xTicks[this.xTicks.length] = i;
54968                 tickMap[i] = true;
54969             }
54970         }
54971
54972         for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
54973             if (!tickMap[i]) {
54974                 this.xTicks[this.xTicks.length] = i;
54975                 tickMap[i] = true;
54976             }
54977         }
54978
54979         Ext.Array.sort(this.xTicks, this.DDMInstance.numericSort);
54980     },
54981
54982     /**
54983      * Creates the array of vertical tick marks if an interval was specified in
54984      * setYConstraint().
54985      * @private
54986      */
54987     setYTicks: function(iStartY, iTickSize) {
54988         this.yTicks = [];
54989         this.yTickSize = iTickSize;
54990
54991         var tickMap = {};
54992
54993         for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
54994             if (!tickMap[i]) {
54995                 this.yTicks[this.yTicks.length] = i;
54996                 tickMap[i] = true;
54997             }
54998         }
54999
55000         for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
55001             if (!tickMap[i]) {
55002                 this.yTicks[this.yTicks.length] = i;
55003                 tickMap[i] = true;
55004             }
55005         }
55006
55007         Ext.Array.sort(this.yTicks, this.DDMInstance.numericSort);
55008     },
55009
55010     /**
55011      * By default, the element can be dragged any place on the screen.  Use
55012      * this method to limit the horizontal travel of the element.  Pass in
55013      * 0,0 for the parameters if you want to lock the drag to the y axis.
55014      * @param {Number} iLeft the number of pixels the element can move to the left
55015      * @param {Number} iRight the number of pixels the element can move to the
55016      * right
55017      * @param {Number} iTickSize (optional) parameter for specifying that the
55018      * element should move iTickSize pixels at a time.
55019      */
55020     setXConstraint: function(iLeft, iRight, iTickSize) {
55021         this.leftConstraint = iLeft;
55022         this.rightConstraint = iRight;
55023
55024         this.minX = this.initPageX - iLeft;
55025         this.maxX = this.initPageX + iRight;
55026         if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
55027
55028         this.constrainX = true;
55029     },
55030
55031     /**
55032      * Clears any constraints applied to this instance.  Also clears ticks
55033      * since they can't exist independent of a constraint at this time.
55034      */
55035     clearConstraints: function() {
55036         this.constrainX = false;
55037         this.constrainY = false;
55038         this.clearTicks();
55039     },
55040
55041     /**
55042      * Clears any tick interval defined for this instance
55043      */
55044     clearTicks: function() {
55045         this.xTicks = null;
55046         this.yTicks = null;
55047         this.xTickSize = 0;
55048         this.yTickSize = 0;
55049     },
55050
55051     /**
55052      * By default, the element can be dragged any place on the screen.  Set
55053      * this to limit the vertical travel of the element.  Pass in 0,0 for the
55054      * parameters if you want to lock the drag to the x axis.
55055      * @param {Number} iUp the number of pixels the element can move up
55056      * @param {Number} iDown the number of pixels the element can move down
55057      * @param {Number} iTickSize (optional) parameter for specifying that the
55058      * element should move iTickSize pixels at a time.
55059      */
55060     setYConstraint: function(iUp, iDown, iTickSize) {
55061         this.topConstraint = iUp;
55062         this.bottomConstraint = iDown;
55063
55064         this.minY = this.initPageY - iUp;
55065         this.maxY = this.initPageY + iDown;
55066         if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
55067
55068         this.constrainY = true;
55069
55070     },
55071
55072     /**
55073      * Must be called if you manually reposition a dd element.
55074      * @param {Boolean} maintainOffset
55075      */
55076     resetConstraints: function() {
55077         // Maintain offsets if necessary
55078         if (this.initPageX || this.initPageX === 0) {
55079             // figure out how much this thing has moved
55080             var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
55081             var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
55082
55083             this.setInitPosition(dx, dy);
55084
55085         // This is the first time we have detected the element's position
55086         } else {
55087             this.setInitPosition();
55088         }
55089
55090         if (this.constrainX) {
55091             this.setXConstraint( this.leftConstraint,
55092                                  this.rightConstraint,
55093                                  this.xTickSize        );
55094         }
55095
55096         if (this.constrainY) {
55097             this.setYConstraint( this.topConstraint,
55098                                  this.bottomConstraint,
55099                                  this.yTickSize         );
55100         }
55101     },
55102
55103     /**
55104      * Normally the drag element is moved pixel by pixel, but we can specify
55105      * that it move a number of pixels at a time.  This method resolves the
55106      * location when we have it set up like this.
55107      * @param {Number} val where we want to place the object
55108      * @param {Number[]} tickArray sorted array of valid points
55109      * @return {Number} the closest tick
55110      * @private
55111      */
55112     getTick: function(val, tickArray) {
55113         if (!tickArray) {
55114             // If tick interval is not defined, it is effectively 1 pixel,
55115             // so we return the value passed to us.
55116             return val;
55117         } else if (tickArray[0] >= val) {
55118             // The value is lower than the first tick, so we return the first
55119             // tick.
55120             return tickArray[0];
55121         } else {
55122             for (var i=0, len=tickArray.length; i<len; ++i) {
55123                 var next = i + 1;
55124                 if (tickArray[next] && tickArray[next] >= val) {
55125                     var diff1 = val - tickArray[i];
55126                     var diff2 = tickArray[next] - val;
55127                     return (diff2 > diff1) ? tickArray[i] : tickArray[next];
55128                 }
55129             }
55130
55131             // The value is larger than the last tick, so we return the last
55132             // tick.
55133             return tickArray[tickArray.length - 1];
55134         }
55135     },
55136
55137     /**
55138      * toString method
55139      * @return {String} string representation of the dd obj
55140      */
55141     toString: function() {
55142         return ("DragDrop " + this.id);
55143     }
55144
55145 });
55146
55147 /*
55148  * This is a derivative of the similarly named class in the YUI Library.
55149  * The original license:
55150  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
55151  * Code licensed under the BSD License:
55152  * http://developer.yahoo.net/yui/license.txt
55153  */
55154
55155
55156 /**
55157  * @class Ext.dd.DD
55158  * A DragDrop implementation where the linked element follows the
55159  * mouse cursor during a drag.
55160  * @extends Ext.dd.DragDrop
55161  */
55162 Ext.define('Ext.dd.DD', {
55163     extend: 'Ext.dd.DragDrop',
55164     requires: ['Ext.dd.DragDropManager'],
55165
55166     /**
55167      * Creates new DD instance.
55168      * @param {String} id the id of the linked element
55169      * @param {String} sGroup the group of related DragDrop items
55170      * @param {Object} config an object containing configurable attributes.
55171      * Valid properties for DD: scroll
55172      */
55173     constructor: function(id, sGroup, config) {
55174         if (id) {
55175             this.init(id, sGroup, config);
55176         }
55177     },
55178
55179     /**
55180      * When set to true, the utility automatically tries to scroll the browser
55181      * window when a drag and drop element is dragged near the viewport boundary.
55182      * Defaults to true.
55183      * @property scroll
55184      * @type Boolean
55185      */
55186     scroll: true,
55187
55188     /**
55189      * Sets the pointer offset to the distance between the linked element's top
55190      * left corner and the location the element was clicked
55191      * @method autoOffset
55192      * @param {Number} iPageX the X coordinate of the click
55193      * @param {Number} iPageY the Y coordinate of the click
55194      */
55195     autoOffset: function(iPageX, iPageY) {
55196         var x = iPageX - this.startPageX;
55197         var y = iPageY - this.startPageY;
55198         this.setDelta(x, y);
55199     },
55200
55201     /**
55202      * Sets the pointer offset.  You can call this directly to force the
55203      * offset to be in a particular location (e.g., pass in 0,0 to set it
55204      * to the center of the object)
55205      * @method setDelta
55206      * @param {Number} iDeltaX the distance from the left
55207      * @param {Number} iDeltaY the distance from the top
55208      */
55209     setDelta: function(iDeltaX, iDeltaY) {
55210         this.deltaX = iDeltaX;
55211         this.deltaY = iDeltaY;
55212     },
55213
55214     /**
55215      * Sets the drag element to the location of the mousedown or click event,
55216      * maintaining the cursor location relative to the location on the element
55217      * that was clicked.  Override this if you want to place the element in a
55218      * location other than where the cursor is.
55219      * @method setDragElPos
55220      * @param {Number} iPageX the X coordinate of the mousedown or drag event
55221      * @param {Number} iPageY the Y coordinate of the mousedown or drag event
55222      */
55223     setDragElPos: function(iPageX, iPageY) {
55224         // the first time we do this, we are going to check to make sure
55225         // the element has css positioning
55226
55227         var el = this.getDragEl();
55228         this.alignElWithMouse(el, iPageX, iPageY);
55229     },
55230
55231     /**
55232      * Sets the element to the location of the mousedown or click event,
55233      * maintaining the cursor location relative to the location on the element
55234      * that was clicked.  Override this if you want to place the element in a
55235      * location other than where the cursor is.
55236      * @method alignElWithMouse
55237      * @param {HTMLElement} el the element to move
55238      * @param {Number} iPageX the X coordinate of the mousedown or drag event
55239      * @param {Number} iPageY the Y coordinate of the mousedown or drag event
55240      */
55241     alignElWithMouse: function(el, iPageX, iPageY) {
55242         var oCoord = this.getTargetCoord(iPageX, iPageY),
55243             fly = el.dom ? el : Ext.fly(el, '_dd'),
55244             elSize = fly.getSize(),
55245             EL = Ext.Element,
55246             vpSize;
55247
55248         if (!this.deltaSetXY) {
55249             vpSize = this.cachedViewportSize = { width: EL.getDocumentWidth(), height: EL.getDocumentHeight() };
55250             var aCoord = [
55251                 Math.max(0, Math.min(oCoord.x, vpSize.width - elSize.width)),
55252                 Math.max(0, Math.min(oCoord.y, vpSize.height - elSize.height))
55253             ];
55254             fly.setXY(aCoord);
55255             var newLeft = fly.getLeft(true);
55256             var newTop  = fly.getTop(true);
55257             this.deltaSetXY = [newLeft - oCoord.x, newTop - oCoord.y];
55258         } else {
55259             vpSize = this.cachedViewportSize;
55260             fly.setLeftTop(
55261                 Math.max(0, Math.min(oCoord.x + this.deltaSetXY[0], vpSize.width - elSize.width)),
55262                 Math.max(0, Math.min(oCoord.y + this.deltaSetXY[1], vpSize.height - elSize.height))
55263             );
55264         }
55265
55266         this.cachePosition(oCoord.x, oCoord.y);
55267         this.autoScroll(oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
55268         return oCoord;
55269     },
55270
55271     /**
55272      * Saves the most recent position so that we can reset the constraints and
55273      * tick marks on-demand.  We need to know this so that we can calculate the
55274      * number of pixels the element is offset from its original position.
55275      * @method cachePosition
55276      * @param {Number} iPageX (optional) the current x position (this just makes it so we
55277      * don't have to look it up again)
55278      * @param {Number} iPageY (optional) the current y position (this just makes it so we
55279      * don't have to look it up again)
55280      */
55281     cachePosition: function(iPageX, iPageY) {
55282         if (iPageX) {
55283             this.lastPageX = iPageX;
55284             this.lastPageY = iPageY;
55285         } else {
55286             var aCoord = Ext.Element.getXY(this.getEl());
55287             this.lastPageX = aCoord[0];
55288             this.lastPageY = aCoord[1];
55289         }
55290     },
55291
55292     /**
55293      * Auto-scroll the window if the dragged object has been moved beyond the
55294      * visible window boundary.
55295      * @method autoScroll
55296      * @param {Number} x the drag element's x position
55297      * @param {Number} y the drag element's y position
55298      * @param {Number} h the height of the drag element
55299      * @param {Number} w the width of the drag element
55300      * @private
55301      */
55302     autoScroll: function(x, y, h, w) {
55303
55304         if (this.scroll) {
55305             // The client height
55306             var clientH = Ext.Element.getViewHeight();
55307
55308             // The client width
55309             var clientW = Ext.Element.getViewWidth();
55310
55311             // The amt scrolled down
55312             var st = this.DDMInstance.getScrollTop();
55313
55314             // The amt scrolled right
55315             var sl = this.DDMInstance.getScrollLeft();
55316
55317             // Location of the bottom of the element
55318             var bot = h + y;
55319
55320             // Location of the right of the element
55321             var right = w + x;
55322
55323             // The distance from the cursor to the bottom of the visible area,
55324             // adjusted so that we don't scroll if the cursor is beyond the
55325             // element drag constraints
55326             var toBot = (clientH + st - y - this.deltaY);
55327
55328             // The distance from the cursor to the right of the visible area
55329             var toRight = (clientW + sl - x - this.deltaX);
55330
55331
55332             // How close to the edge the cursor must be before we scroll
55333             // var thresh = (document.all) ? 100 : 40;
55334             var thresh = 40;
55335
55336             // How many pixels to scroll per autoscroll op.  This helps to reduce
55337             // clunky scrolling. IE is more sensitive about this ... it needs this
55338             // value to be higher.
55339             var scrAmt = (document.all) ? 80 : 30;
55340
55341             // Scroll down if we are near the bottom of the visible page and the
55342             // obj extends below the crease
55343             if ( bot > clientH && toBot < thresh ) {
55344                 window.scrollTo(sl, st + scrAmt);
55345             }
55346
55347             // Scroll up if the window is scrolled down and the top of the object
55348             // goes above the top border
55349             if ( y < st && st > 0 && y - st < thresh ) {
55350                 window.scrollTo(sl, st - scrAmt);
55351             }
55352
55353             // Scroll right if the obj is beyond the right border and the cursor is
55354             // near the border.
55355             if ( right > clientW && toRight < thresh ) {
55356                 window.scrollTo(sl + scrAmt, st);
55357             }
55358
55359             // Scroll left if the window has been scrolled to the right and the obj
55360             // extends past the left border
55361             if ( x < sl && sl > 0 && x - sl < thresh ) {
55362                 window.scrollTo(sl - scrAmt, st);
55363             }
55364         }
55365     },
55366
55367     /**
55368      * Finds the location the element should be placed if we want to move
55369      * it to where the mouse location less the click offset would place us.
55370      * @method getTargetCoord
55371      * @param {Number} iPageX the X coordinate of the click
55372      * @param {Number} iPageY the Y coordinate of the click
55373      * @return an object that contains the coordinates (Object.x and Object.y)
55374      * @private
55375      */
55376     getTargetCoord: function(iPageX, iPageY) {
55377         var x = iPageX - this.deltaX;
55378         var y = iPageY - this.deltaY;
55379
55380         if (this.constrainX) {
55381             if (x < this.minX) {
55382                 x = this.minX;
55383             }
55384             if (x > this.maxX) {
55385                 x = this.maxX;
55386             }
55387         }
55388
55389         if (this.constrainY) {
55390             if (y < this.minY) {
55391                 y = this.minY;
55392             }
55393             if (y > this.maxY) {
55394                 y = this.maxY;
55395             }
55396         }
55397
55398         x = this.getTick(x, this.xTicks);
55399         y = this.getTick(y, this.yTicks);
55400
55401
55402         return {x: x, y: y};
55403     },
55404
55405     /**
55406      * Sets up config options specific to this class. Overrides
55407      * Ext.dd.DragDrop, but all versions of this method through the
55408      * inheritance chain are called
55409      */
55410     applyConfig: function() {
55411         this.callParent();
55412         this.scroll = (this.config.scroll !== false);
55413     },
55414
55415     /**
55416      * Event that fires prior to the onMouseDown event.  Overrides
55417      * Ext.dd.DragDrop.
55418      */
55419     b4MouseDown: function(e) {
55420         // this.resetConstraints();
55421         this.autoOffset(e.getPageX(), e.getPageY());
55422     },
55423
55424     /**
55425      * Event that fires prior to the onDrag event.  Overrides
55426      * Ext.dd.DragDrop.
55427      */
55428     b4Drag: function(e) {
55429         this.setDragElPos(e.getPageX(), e.getPageY());
55430     },
55431
55432     toString: function() {
55433         return ("DD " + this.id);
55434     }
55435
55436     //////////////////////////////////////////////////////////////////////////
55437     // Debugging ygDragDrop events that can be overridden
55438     //////////////////////////////////////////////////////////////////////////
55439     /*
55440     startDrag: function(x, y) {
55441     },
55442
55443     onDrag: function(e) {
55444     },
55445
55446     onDragEnter: function(e, id) {
55447     },
55448
55449     onDragOver: function(e, id) {
55450     },
55451
55452     onDragOut: function(e, id) {
55453     },
55454
55455     onDragDrop: function(e, id) {
55456     },
55457
55458     endDrag: function(e) {
55459     }
55460
55461     */
55462
55463 });
55464
55465 /*
55466  * This is a derivative of the similarly named class in the YUI Library.
55467  * The original license:
55468  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
55469  * Code licensed under the BSD License:
55470  * http://developer.yahoo.net/yui/license.txt
55471  */
55472
55473 /**
55474  * @class Ext.dd.DDProxy
55475  * @extends Ext.dd.DD
55476  * A DragDrop implementation that inserts an empty, bordered div into
55477  * the document that follows the cursor during drag operations.  At the time of
55478  * the click, the frame div is resized to the dimensions of the linked html
55479  * element, and moved to the exact location of the linked element.
55480  *
55481  * References to the "frame" element refer to the single proxy element that
55482  * was created to be dragged in place of all DDProxy elements on the
55483  * page.
55484  */
55485 Ext.define('Ext.dd.DDProxy', {
55486     extend: 'Ext.dd.DD',
55487
55488     statics: {
55489         /**
55490          * The default drag frame div id
55491          * @static
55492          */
55493         dragElId: "ygddfdiv"
55494     },
55495
55496     /**
55497      * Creates new DDProxy.
55498      * @param {String} id the id of the linked html element
55499      * @param {String} sGroup the group of related DragDrop objects
55500      * @param {Object} config an object containing configurable attributes.
55501      * Valid properties for DDProxy in addition to those in DragDrop:
55502      * 
55503      * - resizeFrame
55504      * - centerFrame
55505      * - dragElId
55506      */
55507     constructor: function(id, sGroup, config) {
55508         if (id) {
55509             this.init(id, sGroup, config);
55510             this.initFrame();
55511         }
55512     },
55513
55514     /**
55515      * By default we resize the drag frame to be the same size as the element
55516      * we want to drag (this is to get the frame effect).  We can turn it off
55517      * if we want a different behavior.
55518      * @property resizeFrame
55519      * @type Boolean
55520      */
55521     resizeFrame: true,
55522
55523     /**
55524      * By default the frame is positioned exactly where the drag element is, so
55525      * we use the cursor offset provided by Ext.dd.DD.  Another option that works only if
55526      * you do not have constraints on the obj is to have the drag frame centered
55527      * around the cursor.  Set centerFrame to true for this effect.
55528      * @property centerFrame
55529      * @type Boolean
55530      */
55531     centerFrame: false,
55532
55533     /**
55534      * Creates the proxy element if it does not yet exist
55535      * @method createFrame
55536      */
55537     createFrame: function() {
55538         var self = this;
55539         var body = document.body;
55540
55541         if (!body || !body.firstChild) {
55542             setTimeout( function() { self.createFrame(); }, 50 );
55543             return;
55544         }
55545
55546         var div = this.getDragEl();
55547
55548         if (!div) {
55549             div    = document.createElement("div");
55550             div.id = this.dragElId;
55551             var s  = div.style;
55552
55553             s.position   = "absolute";
55554             s.visibility = "hidden";
55555             s.cursor     = "move";
55556             s.border     = "2px solid #aaa";
55557             s.zIndex     = 999;
55558
55559             // appendChild can blow up IE if invoked prior to the window load event
55560             // while rendering a table.  It is possible there are other scenarios
55561             // that would cause this to happen as well.
55562             body.insertBefore(div, body.firstChild);
55563         }
55564     },
55565
55566     /**
55567      * Initialization for the drag frame element.  Must be called in the
55568      * constructor of all subclasses
55569      * @method initFrame
55570      */
55571     initFrame: function() {
55572         this.createFrame();
55573     },
55574
55575     applyConfig: function() {
55576         this.callParent();
55577
55578         this.resizeFrame = (this.config.resizeFrame !== false);
55579         this.centerFrame = (this.config.centerFrame);
55580         this.setDragElId(this.config.dragElId || Ext.dd.DDProxy.dragElId);
55581     },
55582
55583     /**
55584      * Resizes the drag frame to the dimensions of the clicked object, positions
55585      * it over the object, and finally displays it
55586      * @method showFrame
55587      * @param {Number} iPageX X click position
55588      * @param {Number} iPageY Y click position
55589      * @private
55590      */
55591     showFrame: function(iPageX, iPageY) {
55592         var el = this.getEl();
55593         var dragEl = this.getDragEl();
55594         var s = dragEl.style;
55595
55596         this._resizeProxy();
55597
55598         if (this.centerFrame) {
55599             this.setDelta( Math.round(parseInt(s.width,  10)/2),
55600                            Math.round(parseInt(s.height, 10)/2) );
55601         }
55602
55603         this.setDragElPos(iPageX, iPageY);
55604
55605         Ext.fly(dragEl).show();
55606     },
55607
55608     /**
55609      * The proxy is automatically resized to the dimensions of the linked
55610      * element when a drag is initiated, unless resizeFrame is set to false
55611      * @method _resizeProxy
55612      * @private
55613      */
55614     _resizeProxy: function() {
55615         if (this.resizeFrame) {
55616             var el = this.getEl();
55617             Ext.fly(this.getDragEl()).setSize(el.offsetWidth, el.offsetHeight);
55618         }
55619     },
55620
55621     // overrides Ext.dd.DragDrop
55622     b4MouseDown: function(e) {
55623         var x = e.getPageX();
55624         var y = e.getPageY();
55625         this.autoOffset(x, y);
55626         this.setDragElPos(x, y);
55627     },
55628
55629     // overrides Ext.dd.DragDrop
55630     b4StartDrag: function(x, y) {
55631         // show the drag frame
55632         this.showFrame(x, y);
55633     },
55634
55635     // overrides Ext.dd.DragDrop
55636     b4EndDrag: function(e) {
55637         Ext.fly(this.getDragEl()).hide();
55638     },
55639
55640     // overrides Ext.dd.DragDrop
55641     // By default we try to move the element to the last location of the frame.
55642     // This is so that the default behavior mirrors that of Ext.dd.DD.
55643     endDrag: function(e) {
55644
55645         var lel = this.getEl();
55646         var del = this.getDragEl();
55647
55648         // Show the drag frame briefly so we can get its position
55649         del.style.visibility = "";
55650
55651         this.beforeMove();
55652         // Hide the linked element before the move to get around a Safari
55653         // rendering bug.
55654         lel.style.visibility = "hidden";
55655         Ext.dd.DDM.moveToEl(lel, del);
55656         del.style.visibility = "hidden";
55657         lel.style.visibility = "";
55658
55659         this.afterDrag();
55660     },
55661
55662     beforeMove : function(){
55663
55664     },
55665
55666     afterDrag : function(){
55667
55668     },
55669
55670     toString: function() {
55671         return ("DDProxy " + this.id);
55672     }
55673
55674 });
55675
55676 /**
55677  * @class Ext.dd.DragSource
55678  * @extends Ext.dd.DDProxy
55679  * A simple class that provides the basic implementation needed to make any element draggable.
55680  */
55681 Ext.define('Ext.dd.DragSource', {
55682     extend: 'Ext.dd.DDProxy',
55683     requires: [
55684         'Ext.dd.StatusProxy',
55685         'Ext.dd.DragDropManager'
55686     ],
55687
55688     /**
55689      * @cfg {String} ddGroup
55690      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
55691      * interact with other drag drop objects in the same group.
55692      */
55693
55694     /**
55695      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
55696      * The CSS class returned to the drag source when drop is allowed.
55697      */
55698     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
55699     /**
55700      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
55701      * The CSS class returned to the drag source when drop is not allowed.
55702      */
55703     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
55704
55705     /**
55706      * @cfg {Boolean} animRepair
55707      * If true, animates the proxy element back to the position of the handle element used to trigger the drag.
55708      */
55709     animRepair: true,
55710
55711     /**
55712      * @cfg {String} repairHighlightColor
55713      * The color to use when visually highlighting the drag source in the afterRepair
55714      * method after a failed drop (defaults to light blue). The color must be a 6 digit hex value, without
55715      * a preceding '#'.
55716      */
55717     repairHighlightColor: 'c3daf9',
55718
55719     /**
55720      * Creates new drag-source.
55721      * @constructor
55722      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
55723      * @param {Object} config (optional) Config object.
55724      */
55725     constructor: function(el, config) {
55726         this.el = Ext.get(el);
55727         if(!this.dragData){
55728             this.dragData = {};
55729         }
55730
55731         Ext.apply(this, config);
55732
55733         if(!this.proxy){
55734             this.proxy = Ext.create('Ext.dd.StatusProxy', {
55735                 animRepair: this.animRepair
55736             });
55737         }
55738         this.callParent([this.el.dom, this.ddGroup || this.group,
55739               {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true}]);
55740
55741         this.dragging = false;
55742     },
55743
55744     /**
55745      * Returns the data object associated with this drag source
55746      * @return {Object} data An object containing arbitrary data
55747      */
55748     getDragData : function(e){
55749         return this.dragData;
55750     },
55751
55752     // private
55753     onDragEnter : function(e, id){
55754         var target = Ext.dd.DragDropManager.getDDById(id);
55755         this.cachedTarget = target;
55756         if (this.beforeDragEnter(target, e, id) !== false) {
55757             if (target.isNotifyTarget) {
55758                 var status = target.notifyEnter(this, e, this.dragData);
55759                 this.proxy.setStatus(status);
55760             } else {
55761                 this.proxy.setStatus(this.dropAllowed);
55762             }
55763
55764             if (this.afterDragEnter) {
55765                 /**
55766                  * An empty function by default, but provided so that you can perform a custom action
55767                  * when the dragged item enters the drop target by providing an implementation.
55768                  * @param {Ext.dd.DragDrop} target The drop target
55769                  * @param {Event} e The event object
55770                  * @param {String} id The id of the dragged element
55771                  * @method afterDragEnter
55772                  */
55773                 this.afterDragEnter(target, e, id);
55774             }
55775         }
55776     },
55777
55778     /**
55779      * An empty function by default, but provided so that you can perform a custom action
55780      * before the dragged item enters the drop target and optionally cancel the onDragEnter.
55781      * @param {Ext.dd.DragDrop} target The drop target
55782      * @param {Event} e The event object
55783      * @param {String} id The id of the dragged element
55784      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
55785      */
55786     beforeDragEnter: function(target, e, id) {
55787         return true;
55788     },
55789
55790     // private
55791     alignElWithMouse: function() {
55792         this.callParent(arguments);
55793         this.proxy.sync();
55794     },
55795
55796     // private
55797     onDragOver: function(e, id) {
55798         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
55799         if (this.beforeDragOver(target, e, id) !== false) {
55800             if(target.isNotifyTarget){
55801                 var status = target.notifyOver(this, e, this.dragData);
55802                 this.proxy.setStatus(status);
55803             }
55804
55805             if (this.afterDragOver) {
55806                 /**
55807                  * An empty function by default, but provided so that you can perform a custom action
55808                  * while the dragged item is over the drop target by providing an implementation.
55809                  * @param {Ext.dd.DragDrop} target The drop target
55810                  * @param {Event} e The event object
55811                  * @param {String} id The id of the dragged element
55812                  * @method afterDragOver
55813                  */
55814                 this.afterDragOver(target, e, id);
55815             }
55816         }
55817     },
55818
55819     /**
55820      * An empty function by default, but provided so that you can perform a custom action
55821      * while the dragged item is over the drop target and optionally cancel the onDragOver.
55822      * @param {Ext.dd.DragDrop} target The drop target
55823      * @param {Event} e The event object
55824      * @param {String} id The id of the dragged element
55825      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
55826      */
55827     beforeDragOver: function(target, e, id) {
55828         return true;
55829     },
55830
55831     // private
55832     onDragOut: function(e, id) {
55833         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
55834         if (this.beforeDragOut(target, e, id) !== false) {
55835             if (target.isNotifyTarget) {
55836                 target.notifyOut(this, e, this.dragData);
55837             }
55838             this.proxy.reset();
55839             if (this.afterDragOut) {
55840                 /**
55841                  * An empty function by default, but provided so that you can perform a custom action
55842                  * after the dragged item is dragged out of the target without dropping.
55843                  * @param {Ext.dd.DragDrop} target The drop target
55844                  * @param {Event} e The event object
55845                  * @param {String} id The id of the dragged element
55846                  * @method afterDragOut
55847                  */
55848                 this.afterDragOut(target, e, id);
55849             }
55850         }
55851         this.cachedTarget = null;
55852     },
55853
55854     /**
55855      * An empty function by default, but provided so that you can perform a custom action before the dragged
55856      * item is dragged out of the target without dropping, and optionally cancel the onDragOut.
55857      * @param {Ext.dd.DragDrop} target The drop target
55858      * @param {Event} e The event object
55859      * @param {String} id The id of the dragged element
55860      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
55861      */
55862     beforeDragOut: function(target, e, id){
55863         return true;
55864     },
55865
55866     // private
55867     onDragDrop: function(e, id){
55868         var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
55869         if (this.beforeDragDrop(target, e, id) !== false) {
55870             if (target.isNotifyTarget) {
55871                 if (target.notifyDrop(this, e, this.dragData) !== false) { // valid drop?
55872                     this.onValidDrop(target, e, id);
55873                 } else {
55874                     this.onInvalidDrop(target, e, id);
55875                 }
55876             } else {
55877                 this.onValidDrop(target, e, id);
55878             }
55879
55880             if (this.afterDragDrop) {
55881                 /**
55882                  * An empty function by default, but provided so that you can perform a custom action
55883                  * after a valid drag drop has occurred by providing an implementation.
55884                  * @param {Ext.dd.DragDrop} target The drop target
55885                  * @param {Event} e The event object
55886                  * @param {String} id The id of the dropped element
55887                  * @method afterDragDrop
55888                  */
55889                 this.afterDragDrop(target, e, id);
55890             }
55891         }
55892         delete this.cachedTarget;
55893     },
55894
55895     /**
55896      * An empty function by default, but provided so that you can perform a custom action before the dragged
55897      * item is dropped onto the target and optionally cancel the onDragDrop.
55898      * @param {Ext.dd.DragDrop} target The drop target
55899      * @param {Event} e The event object
55900      * @param {String} id The id of the dragged element
55901      * @return {Boolean} isValid True if the drag drop event is valid, else false to cancel
55902      */
55903     beforeDragDrop: function(target, e, id){
55904         return true;
55905     },
55906
55907     // private
55908     onValidDrop: function(target, e, id){
55909         this.hideProxy();
55910         if(this.afterValidDrop){
55911             /**
55912              * An empty function by default, but provided so that you can perform a custom action
55913              * after a valid drop has occurred by providing an implementation.
55914              * @param {Object} target The target DD
55915              * @param {Event} e The event object
55916              * @param {String} id The id of the dropped element
55917              * @method afterValidDrop
55918              */
55919             this.afterValidDrop(target, e, id);
55920         }
55921     },
55922
55923     // private
55924     getRepairXY: function(e, data){
55925         return this.el.getXY();
55926     },
55927
55928     // private
55929     onInvalidDrop: function(target, e, id) {
55930         this.beforeInvalidDrop(target, e, id);
55931         if (this.cachedTarget) {
55932             if(this.cachedTarget.isNotifyTarget){
55933                 this.cachedTarget.notifyOut(this, e, this.dragData);
55934             }
55935             this.cacheTarget = null;
55936         }
55937         this.proxy.repair(this.getRepairXY(e, this.dragData), this.afterRepair, this);
55938
55939         if (this.afterInvalidDrop) {
55940             /**
55941              * An empty function by default, but provided so that you can perform a custom action
55942              * after an invalid drop has occurred by providing an implementation.
55943              * @param {Event} e The event object
55944              * @param {String} id The id of the dropped element
55945              * @method afterInvalidDrop
55946              */
55947             this.afterInvalidDrop(e, id);
55948         }
55949     },
55950
55951     // private
55952     afterRepair: function() {
55953         var me = this;
55954         if (Ext.enableFx) {
55955             me.el.highlight(me.repairHighlightColor);
55956         }
55957         me.dragging = false;
55958     },
55959
55960     /**
55961      * An empty function by default, but provided so that you can perform a custom action after an invalid
55962      * drop has occurred.
55963      * @param {Ext.dd.DragDrop} target The drop target
55964      * @param {Event} e The event object
55965      * @param {String} id The id of the dragged element
55966      * @return {Boolean} isValid True if the invalid drop should proceed, else false to cancel
55967      */
55968     beforeInvalidDrop: function(target, e, id) {
55969         return true;
55970     },
55971
55972     // private
55973     handleMouseDown: function(e) {
55974         if (this.dragging) {
55975             return;
55976         }
55977         var data = this.getDragData(e);
55978         if (data && this.onBeforeDrag(data, e) !== false) {
55979             this.dragData = data;
55980             this.proxy.stop();
55981             this.callParent(arguments);
55982         }
55983     },
55984
55985     /**
55986      * An empty function by default, but provided so that you can perform a custom action before the initial
55987      * drag event begins and optionally cancel it.
55988      * @param {Object} data An object containing arbitrary data to be shared with drop targets
55989      * @param {Event} e The event object
55990      * @return {Boolean} isValid True if the drag event is valid, else false to cancel
55991      */
55992     onBeforeDrag: function(data, e){
55993         return true;
55994     },
55995
55996     /**
55997      * An empty function by default, but provided so that you can perform a custom action once the initial
55998      * drag event has begun.  The drag cannot be canceled from this function.
55999      * @param {Number} x The x position of the click on the dragged object
56000      * @param {Number} y The y position of the click on the dragged object
56001      * @method
56002      */
56003     onStartDrag: Ext.emptyFn,
56004
56005     // private override
56006     startDrag: function(x, y) {
56007         this.proxy.reset();
56008         this.dragging = true;
56009         this.proxy.update("");
56010         this.onInitDrag(x, y);
56011         this.proxy.show();
56012     },
56013
56014     // private
56015     onInitDrag: function(x, y) {
56016         var clone = this.el.dom.cloneNode(true);
56017         clone.id = Ext.id(); // prevent duplicate ids
56018         this.proxy.update(clone);
56019         this.onStartDrag(x, y);
56020         return true;
56021     },
56022
56023     /**
56024      * Returns the drag source's underlying {@link Ext.dd.StatusProxy}
56025      * @return {Ext.dd.StatusProxy} proxy The StatusProxy
56026      */
56027     getProxy: function() {
56028         return this.proxy;
56029     },
56030
56031     /**
56032      * Hides the drag source's {@link Ext.dd.StatusProxy}
56033      */
56034     hideProxy: function() {
56035         this.proxy.hide();
56036         this.proxy.reset(true);
56037         this.dragging = false;
56038     },
56039
56040     // private
56041     triggerCacheRefresh: function() {
56042         Ext.dd.DDM.refreshCache(this.groups);
56043     },
56044
56045     // private - override to prevent hiding
56046     b4EndDrag: function(e) {
56047     },
56048
56049     // private - override to prevent moving
56050     endDrag : function(e){
56051         this.onEndDrag(this.dragData, e);
56052     },
56053
56054     // private
56055     onEndDrag : function(data, e){
56056     },
56057
56058     // private - pin to cursor
56059     autoOffset : function(x, y) {
56060         this.setDelta(-12, -20);
56061     },
56062
56063     destroy: function(){
56064         this.callParent();
56065         Ext.destroy(this.proxy);
56066     }
56067 });
56068
56069 // private - DD implementation for Panels
56070 Ext.define('Ext.panel.DD', {
56071     extend: 'Ext.dd.DragSource',
56072     requires: ['Ext.panel.Proxy'],
56073
56074     constructor : function(panel, cfg){
56075         this.panel = panel;
56076         this.dragData = {panel: panel};
56077         this.proxy = Ext.create('Ext.panel.Proxy', panel, cfg);
56078
56079         this.callParent([panel.el, cfg]);
56080
56081         Ext.defer(function() {
56082             var header = panel.header,
56083                 el = panel.body;
56084
56085             if(header){
56086                 this.setHandleElId(header.id);
56087                 el = header.el;
56088             }
56089             el.setStyle('cursor', 'move');
56090             this.scroll = false;
56091         }, 200, this);
56092     },
56093
56094     showFrame: Ext.emptyFn,
56095     startDrag: Ext.emptyFn,
56096     b4StartDrag: function(x, y) {
56097         this.proxy.show();
56098     },
56099     b4MouseDown: function(e) {
56100         var x = e.getPageX(),
56101             y = e.getPageY();
56102         this.autoOffset(x, y);
56103     },
56104     onInitDrag : function(x, y){
56105         this.onStartDrag(x, y);
56106         return true;
56107     },
56108     createFrame : Ext.emptyFn,
56109     getDragEl : function(e){
56110         return this.proxy.ghost.el.dom;
56111     },
56112     endDrag : function(e){
56113         this.proxy.hide();
56114         this.panel.saveState();
56115     },
56116
56117     autoOffset : function(x, y) {
56118         x -= this.startPageX;
56119         y -= this.startPageY;
56120         this.setDelta(x, y);
56121     }
56122 });
56123
56124 /**
56125  * @class Ext.layout.component.Dock
56126  * @extends Ext.layout.component.AbstractDock
56127  * @private
56128  */
56129 Ext.define('Ext.layout.component.Dock', {
56130
56131     /* Begin Definitions */
56132
56133     alias: ['layout.dock'],
56134
56135     extend: 'Ext.layout.component.AbstractDock'
56136
56137     /* End Definitions */
56138
56139 });
56140 /**
56141  * Panel is a container that has specific functionality and structural components that make it the perfect building
56142  * block for application-oriented user interfaces.
56143  *
56144  * Panels are, by virtue of their inheritance from {@link Ext.container.Container}, capable of being configured with a
56145  * {@link Ext.container.Container#layout layout}, and containing child Components.
56146  *
56147  * When either specifying child {@link #items} of a Panel, or dynamically {@link Ext.container.Container#add adding}
56148  * Components to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether those
56149  * child elements need to be sized using one of Ext's built-in `{@link Ext.container.Container#layout layout}`
56150  * schemes. By default, Panels use the {@link Ext.layout.container.Auto Auto} scheme. This simply renders child
56151  * components, appending them one after the other inside the Container, and **does not apply any sizing** at all.
56152  *
56153  * {@img Ext.panel.Panel/panel.png Panel components}
56154  *
56155  * A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate {@link
56156  * Ext.panel.Header header}, {@link #fbar footer} and body sections.
56157  *
56158  * Panel also provides built-in {@link #collapsible collapsible, expandable} and {@link #closable} behavior. Panels can
56159  * be easily dropped into any {@link Ext.container.Container Container} or layout, and the layout and rendering pipeline
56160  * is {@link Ext.container.Container#add completely managed by the framework}.
56161  *
56162  * **Note:** By default, the `{@link #closable close}` header tool _destroys_ the Panel resulting in removal of the
56163  * Panel and the destruction of any descendant Components. This makes the Panel object, and all its descendants
56164  * **unusable**. To enable the close tool to simply _hide_ a Panel for later re-use, configure the Panel with
56165  * `{@link #closeAction closeAction}: 'hide'`.
56166  *
56167  * Usually, Panels are used as constituents within an application, in which case, they would be used as child items of
56168  * Containers, and would themselves use Ext.Components as child {@link #items}. However to illustrate simply rendering a
56169  * Panel into the document, here's how to do it:
56170  *
56171  *     @example
56172  *     Ext.create('Ext.panel.Panel', {
56173  *         title: 'Hello',
56174  *         width: 200,
56175  *         html: '<p>World!</p>',
56176  *         renderTo: Ext.getBody()
56177  *     });
56178  *
56179  * A more realistic scenario is a Panel created to house input fields which will not be rendered, but used as a
56180  * constituent part of a Container:
56181  *
56182  *     @example
56183  *     var filterPanel = Ext.create('Ext.panel.Panel', {
56184  *         bodyPadding: 5,  // Don't want content to crunch against the borders
56185  *         width: 300,
56186  *         title: 'Filters',
56187  *         items: [{
56188  *             xtype: 'datefield',
56189  *             fieldLabel: 'Start date'
56190  *         }, {
56191  *             xtype: 'datefield',
56192  *             fieldLabel: 'End date'
56193  *         }],
56194  *         renderTo: Ext.getBody()
56195  *     });
56196  *
56197  * Note that the Panel above is not configured to render into the document, nor is it configured with a size or
56198  * position. In a real world scenario, the Container into which the Panel is added will use a {@link #layout} to render,
56199  * size and position its child Components.
56200  *
56201  * Panels will often use specific {@link #layout}s to provide an application with shape and structure by containing and
56202  * arranging child Components:
56203  *
56204  *     @example
56205  *     var resultsPanel = Ext.create('Ext.panel.Panel', {
56206  *         title: 'Results',
56207  *         width: 600,
56208  *         height: 400,
56209  *         renderTo: Ext.getBody(),
56210  *         layout: {
56211  *             type: 'vbox',       // Arrange child items vertically
56212  *             align: 'stretch',    // Each takes up full width
56213  *             padding: 5
56214  *         },
56215  *         items: [{               // Results grid specified as a config object with an xtype of 'grid'
56216  *             xtype: 'grid',
56217  *             columns: [{header: 'Column One'}],            // One header just for show. There's no data,
56218  *             store: Ext.create('Ext.data.ArrayStore', {}), // A dummy empty data store
56219  *             flex: 1                                       // Use 1/3 of Container's height (hint to Box layout)
56220  *         }, {
56221  *             xtype: 'splitter'   // A splitter between the two child items
56222  *         }, {                    // Details Panel specified as a config object (no xtype defaults to 'panel').
56223  *             title: 'Details',
56224  *             bodyPadding: 5,
56225  *             items: [{
56226  *                 fieldLabel: 'Data item',
56227  *                 xtype: 'textfield'
56228  *             }], // An array of form fields
56229  *             flex: 2             // Use 2/3 of Container's height (hint to Box layout)
56230  *         }]
56231  *     });
56232  *
56233  * The example illustrates one possible method of displaying search results. The Panel contains a grid with the
56234  * resulting data arranged in rows. Each selected row may be displayed in detail in the Panel below. The {@link
56235  * Ext.layout.container.VBox vbox} layout is used to arrange the two vertically. It is configured to stretch child items
56236  * horizontally to full width. Child items may either be configured with a numeric height, or with a `flex` value to
56237  * distribute available space proportionately.
56238  *
56239  * This Panel itself may be a child item of, for exaple, a {@link Ext.tab.Panel} which will size its child items to fit
56240  * within its content area.
56241  *
56242  * Using these techniques, as long as the **layout** is chosen and configured correctly, an application may have any
56243  * level of nested containment, all dynamically sized according to configuration, the user's preference and available
56244  * browser size.
56245  */
56246 Ext.define('Ext.panel.Panel', {
56247     extend: 'Ext.panel.AbstractPanel',
56248     requires: [
56249         'Ext.panel.Header',
56250         'Ext.fx.Anim',
56251         'Ext.util.KeyMap',
56252         'Ext.panel.DD',
56253         'Ext.XTemplate',
56254         'Ext.layout.component.Dock',
56255         'Ext.util.Memento'
56256     ],
56257     alias: 'widget.panel',
56258     alternateClassName: 'Ext.Panel',
56259
56260     /**
56261      * @cfg {String} collapsedCls
56262      * A CSS class to add to the panel's element after it has been collapsed.
56263      */
56264     collapsedCls: 'collapsed',
56265
56266     /**
56267      * @cfg {Boolean} animCollapse
56268      * `true` to animate the transition when the panel is collapsed, `false` to skip the animation (defaults to `true`
56269      * if the {@link Ext.fx.Anim} class is available, otherwise `false`). May also be specified as the animation
56270      * duration in milliseconds.
56271      */
56272     animCollapse: Ext.enableFx,
56273
56274     /**
56275      * @cfg {Number} minButtonWidth
56276      * Minimum width of all footer toolbar buttons in pixels. If set, this will be used as the default
56277      * value for the {@link Ext.button.Button#minWidth} config of each Button added to the **footer toolbar** via the
56278      * {@link #fbar} or {@link #buttons} configurations. It will be ignored for buttons that have a minWidth configured
56279      * some other way, e.g. in their own config object or via the {@link Ext.container.Container#defaults defaults} of
56280      * their parent container.
56281      */
56282     minButtonWidth: 75,
56283
56284     /**
56285      * @cfg {Boolean} collapsed
56286      * `true` to render the panel collapsed, `false` to render it expanded.
56287      */
56288     collapsed: false,
56289
56290     /**
56291      * @cfg {Boolean} collapseFirst
56292      * `true` to make sure the collapse/expand toggle button always renders first (to the left of) any other tools in
56293      * the panel's title bar, `false` to render it last.
56294      */
56295     collapseFirst: true,
56296
56297     /**
56298      * @cfg {Boolean} hideCollapseTool
56299      * `true` to hide the expand/collapse toggle button when `{@link #collapsible} == true`, `false` to display it.
56300      */
56301     hideCollapseTool: false,
56302
56303     /**
56304      * @cfg {Boolean} titleCollapse
56305      * `true` to allow expanding and collapsing the panel (when `{@link #collapsible} = true`) by clicking anywhere in
56306      * the header bar, `false`) to allow it only by clicking to tool butto).
56307      */
56308     titleCollapse: false,
56309
56310     /**
56311      * @cfg {String} collapseMode
56312      * **Important: this config is only effective for {@link #collapsible} Panels which are direct child items of a
56313      * {@link Ext.layout.container.Border border layout}.**
56314      *
56315      * When _not_ a direct child item of a {@link Ext.layout.container.Border border layout}, then the Panel's header
56316      * remains visible, and the body is collapsed to zero dimensions. If the Panel has no header, then a new header
56317      * (orientated correctly depending on the {@link #collapseDirection}) will be inserted to show a the title and a re-
56318      * expand tool.
56319      *
56320      * When a child item of a {@link Ext.layout.container.Border border layout}, this config has two options:
56321      *
56322      * - **`undefined/omitted`**
56323      *
56324      *   When collapsed, a placeholder {@link Ext.panel.Header Header} is injected into the layout to represent the Panel
56325      *   and to provide a UI with a Tool to allow the user to re-expand the Panel.
56326      *
56327      * - **`header`** :
56328      *
56329      *   The Panel collapses to leave its header visible as when not inside a {@link Ext.layout.container.Border border
56330      *   layout}.
56331      */
56332
56333     /**
56334      * @cfg {Ext.Component/Object} placeholder
56335      * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
56336      * {@link Ext.layout.container.Border border layout} when not using the `'header'` {@link #collapseMode}.**
56337      *
56338      * **Optional.** A Component (or config object for a Component) to show in place of this Panel when this Panel is
56339      * collapsed by a {@link Ext.layout.container.Border border layout}. Defaults to a generated {@link Ext.panel.Header
56340      * Header} containing a {@link Ext.panel.Tool Tool} to re-expand the Panel.
56341      */
56342
56343     /**
56344      * @cfg {Boolean} floatable
56345      * **Important: This config is only effective for {@link #collapsible} Panels which are direct child items of a
56346      * {@link Ext.layout.container.Border border layout}.**
56347      *
56348      * true to allow clicking a collapsed Panel's {@link #placeholder} to display the Panel floated above the layout,
56349      * false to force the user to fully expand a collapsed region by clicking the expand button to see it again.
56350      */
56351     floatable: true,
56352
56353     /**
56354      * @cfg {Boolean} overlapHeader
56355      * True to overlap the header in a panel over the framing of the panel itself. This is needed when frame:true (and
56356      * is done automatically for you). Otherwise it is undefined. If you manually add rounded corners to a panel header
56357      * which does not have frame:true, this will need to be set to true.
56358      */
56359
56360     /**
56361      * @cfg {Boolean} collapsible
56362      * True to make the panel collapsible and have an expand/collapse toggle Tool added into the header tool button
56363      * area. False to keep the panel sized either statically, or by an owning layout manager, with no toggle Tool.
56364      *
56365      * See {@link #collapseMode} and {@link #collapseDirection}
56366      */
56367     collapsible: false,
56368
56369     /**
56370      * @cfg {Boolean} collapseDirection
56371      * The direction to collapse the Panel when the toggle button is clicked.
56372      *
56373      * Defaults to the {@link #headerPosition}
56374      *
56375      * **Important: This config is _ignored_ for {@link #collapsible} Panels which are direct child items of a {@link
56376      * Ext.layout.container.Border border layout}.**
56377      *
56378      * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
56379      */
56380
56381     /**
56382      * @cfg {Boolean} closable
56383      * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
56384      * disallow closing the window.
56385      *
56386      * By default, when close is requested by clicking the close button in the header, the {@link #close} method will be
56387      * called. This will _{@link Ext.Component#destroy destroy}_ the Panel and its content meaning that it may not be
56388      * reused.
56389      *
56390      * To make closing a Panel _hide_ the Panel so that it may be reused, set {@link #closeAction} to 'hide'.
56391      */
56392     closable: false,
56393
56394     /**
56395      * @cfg {String} closeAction
56396      * The action to take when the close header tool is clicked:
56397      *
56398      * - **`'{@link #destroy}'`** :
56399      *
56400      *   {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy} it and all descendant
56401      *   Components. The window will **not** be available to be redisplayed via the {@link #show} method.
56402      *
56403      * - **`'{@link #hide}'`** :
56404      *
56405      *   {@link #hide} the window by setting visibility to hidden and applying negative offsets. The window will be
56406      *   available to be redisplayed via the {@link #show} method.
56407      *
56408      * **Note:** This behavior has changed! setting *does* affect the {@link #close} method which will invoke the
56409      * approriate closeAction.
56410      */
56411     closeAction: 'destroy',
56412
56413     /**
56414      * @cfg {Object/Object[]} dockedItems
56415      * A component or series of components to be added as docked items to this panel. The docked items can be docked to
56416      * either the top, right, left or bottom of a panel. This is typically used for things like toolbars or tab bars:
56417      *
56418      *     var panel = new Ext.panel.Panel({
56419      *         dockedItems: [{
56420      *             xtype: 'toolbar',
56421      *             dock: 'top',
56422      *             items: [{
56423      *                 text: 'Docked to the top'
56424      *             }]
56425      *         }]
56426      *     });
56427      */
56428
56429     /**
56430       * @cfg {Boolean} preventHeader
56431       * Prevent a Header from being created and shown.
56432       */
56433     preventHeader: false,
56434
56435      /**
56436       * @cfg {String} headerPosition
56437       * Specify as `'top'`, `'bottom'`, `'left'` or `'right'`.
56438       */
56439     headerPosition: 'top',
56440
56441      /**
56442      * @cfg {Boolean} frame
56443      * True to apply a frame to the panel.
56444      */
56445     frame: false,
56446
56447     /**
56448      * @cfg {Boolean} frameHeader
56449      * True to apply a frame to the panel panels header (if 'frame' is true).
56450      */
56451     frameHeader: true,
56452
56453     /**
56454      * @cfg {Object[]/Ext.panel.Tool[]} tools
56455      * An array of {@link Ext.panel.Tool} configs/instances to be added to the header tool area. The tools are stored as
56456      * child components of the header container. They can be accessed using {@link #down} and {#query}, as well as the
56457      * other component methods. The toggle tool is automatically created if {@link #collapsible} is set to true.
56458      *
56459      * Note that, apart from the toggle tool which is provided when a panel is collapsible, these tools only provide the
56460      * visual button. Any required functionality must be provided by adding handlers that implement the necessary
56461      * behavior.
56462      *
56463      * Example usage:
56464      *
56465      *     tools:[{
56466      *         type:'refresh',
56467      *         tooltip: 'Refresh form Data',
56468      *         // hidden:true,
56469      *         handler: function(event, toolEl, panel){
56470      *             // refresh logic
56471      *         }
56472      *     },
56473      *     {
56474      *         type:'help',
56475      *         tooltip: 'Get Help',
56476      *         handler: function(event, toolEl, panel){
56477      *             // show help here
56478      *         }
56479      *     }]
56480      */
56481
56482     /**
56483      * @cfg {String} [title='']
56484      * The title text to be used to display in the {@link Ext.panel.Header panel header}. When a
56485      * `title` is specified the {@link Ext.panel.Header} will automatically be created and displayed unless
56486      * {@link #preventHeader} is set to `true`.
56487      */
56488
56489     /**
56490      * @cfg {String} iconCls
56491      * CSS class for icon in header. Used for displaying an icon to the left of a title.
56492      */
56493
56494     initComponent: function() {
56495         var me = this,
56496             cls;
56497
56498         me.addEvents(
56499
56500             /**
56501              * @event beforeclose
56502              * Fires before the user closes the panel. Return false from any listener to stop the close event being
56503              * fired
56504              * @param {Ext.panel.Panel} panel The Panel object
56505              */
56506             'beforeclose',
56507
56508             /**
56509              * @event beforeexpand
56510              * Fires before this panel is expanded. Return false to prevent the expand.
56511              * @param {Ext.panel.Panel} p The Panel being expanded.
56512              * @param {Boolean} animate True if the expand is animated, else false.
56513              */
56514             "beforeexpand",
56515
56516             /**
56517              * @event beforecollapse
56518              * Fires before this panel is collapsed. Return false to prevent the collapse.
56519              * @param {Ext.panel.Panel} p The Panel being collapsed.
56520              * @param {String} direction . The direction of the collapse. One of
56521              *
56522              *   - Ext.Component.DIRECTION_TOP
56523              *   - Ext.Component.DIRECTION_RIGHT
56524              *   - Ext.Component.DIRECTION_BOTTOM
56525              *   - Ext.Component.DIRECTION_LEFT
56526              *
56527              * @param {Boolean} animate True if the collapse is animated, else false.
56528              */
56529             "beforecollapse",
56530
56531             /**
56532              * @event expand
56533              * Fires after this Panel has expanded.
56534              * @param {Ext.panel.Panel} p The Panel that has been expanded.
56535              */
56536             "expand",
56537
56538             /**
56539              * @event collapse
56540              * Fires after this Panel hass collapsed.
56541              * @param {Ext.panel.Panel} p The Panel that has been collapsed.
56542              */
56543             "collapse",
56544
56545             /**
56546              * @event titlechange
56547              * Fires after the Panel title has been set or changed.
56548              * @param {Ext.panel.Panel} p the Panel which has been resized.
56549              * @param {String} newTitle The new title.
56550              * @param {String} oldTitle The previous panel title.
56551              */
56552             'titlechange',
56553
56554             /**
56555              * @event iconchange
56556              * Fires after the Panel iconCls has been set or changed.
56557              * @param {Ext.panel.Panel} p the Panel which has been resized.
56558              * @param {String} newIconCls The new iconCls.
56559              * @param {String} oldIconCls The previous panel iconCls.
56560              */
56561             'iconchange'
56562         );
56563
56564         // Save state on these two events.
56565         this.addStateEvents('expand', 'collapse');
56566
56567         if (me.unstyled) {
56568             me.setUI('plain');
56569         }
56570
56571         if (me.frame) {
56572             me.setUI(me.ui + '-framed');
56573         }
56574
56575         // Backwards compatibility
56576         me.bridgeToolbars();
56577
56578         me.callParent();
56579         me.collapseDirection = me.collapseDirection || me.headerPosition || Ext.Component.DIRECTION_TOP;
56580     },
56581
56582     setBorder: function(border) {
56583         // var me     = this,
56584         //     method = (border === false || border === 0) ? 'addClsWithUI' : 'removeClsWithUI';
56585         //
56586         // me.callParent(arguments);
56587         //
56588         // if (me.collapsed) {
56589         //     me[method](me.collapsedCls + '-noborder');
56590         // }
56591         //
56592         // if (me.header) {
56593         //     me.header.setBorder(border);
56594         //     if (me.collapsed) {
56595         //         me.header[method](me.collapsedCls + '-noborder');
56596         //     }
56597         // }
56598
56599         this.callParent(arguments);
56600     },
56601
56602     beforeDestroy: function() {
56603         Ext.destroy(
56604             this.ghostPanel,
56605             this.dd
56606         );
56607         this.callParent();
56608     },
56609
56610     initAria: function() {
56611         this.callParent();
56612         this.initHeaderAria();
56613     },
56614
56615     initHeaderAria: function() {
56616         var me = this,
56617             el = me.el,
56618             header = me.header;
56619         if (el && header) {
56620             el.dom.setAttribute('aria-labelledby', header.titleCmp.id);
56621         }
56622     },
56623
56624     getHeader: function() {
56625         return this.header;
56626     },
56627
56628     /**
56629      * Set a title for the panel's header. See {@link Ext.panel.Header#title}.
56630      * @param {String} newTitle
56631      */
56632     setTitle: function(newTitle) {
56633         var me = this,
56634         oldTitle = this.title;
56635
56636         me.title = newTitle;
56637         if (me.header) {
56638             me.header.setTitle(newTitle);
56639         } else {
56640             me.updateHeader();
56641         }
56642
56643         if (me.reExpander) {
56644             me.reExpander.setTitle(newTitle);
56645         }
56646         me.fireEvent('titlechange', me, newTitle, oldTitle);
56647     },
56648
56649     /**
56650      * Set the iconCls for the panel's header. See {@link Ext.panel.Header#iconCls}. It will fire the
56651      * {@link #iconchange} event after completion.
56652      * @param {String} newIconCls The new CSS class name
56653      */
56654     setIconCls: function(newIconCls) {
56655         var me = this,
56656             oldIconCls = me.iconCls;
56657
56658         me.iconCls = newIconCls;
56659         var header = me.header;
56660         if (header) {
56661             header.setIconCls(newIconCls);
56662         }
56663         me.fireEvent('iconchange', me, newIconCls, oldIconCls);
56664     },
56665
56666     bridgeToolbars: function() {
56667         var me = this,
56668             docked = [],
56669             fbar,
56670             fbarDefaults,
56671             minButtonWidth = me.minButtonWidth;
56672
56673         function initToolbar (toolbar, pos, useButtonAlign) {
56674             if (Ext.isArray(toolbar)) {
56675                 toolbar = {
56676                     xtype: 'toolbar',
56677                     items: toolbar
56678                 };
56679             }
56680             else if (!toolbar.xtype) {
56681                 toolbar.xtype = 'toolbar';
56682             }
56683             toolbar.dock = pos;
56684             if (pos == 'left' || pos == 'right') {
56685                 toolbar.vertical = true;
56686             }
56687
56688             // Legacy support for buttonAlign (only used by buttons/fbar)
56689             if (useButtonAlign) {
56690                 toolbar.layout = Ext.applyIf(toolbar.layout || {}, {
56691                     // default to 'end' (right-aligned) if me.buttonAlign is undefined or invalid
56692                     pack: { left:'start', center:'center' }[me.buttonAlign] || 'end'
56693                 });
56694             }
56695             return toolbar;
56696         }
56697
56698         // Short-hand toolbars (tbar, bbar and fbar plus new lbar and rbar):
56699
56700         /**
56701          * @cfg {String} buttonAlign
56702          * The alignment of any buttons added to this panel. Valid values are 'right', 'left' and 'center' (defaults to
56703          * 'right' for buttons/fbar, 'left' for other toolbar types).
56704          *
56705          * **NOTE:** The prefered way to specify toolbars is to use the dockedItems config. Instead of buttonAlign you
56706          * would add the layout: { pack: 'start' | 'center' | 'end' } option to the dockedItem config.
56707          */
56708
56709         /**
56710          * @cfg {Object/Object[]} tbar
56711          * Convenience config. Short for 'Top Bar'.
56712          *
56713          *     tbar: [
56714          *       { xtype: 'button', text: 'Button 1' }
56715          *     ]
56716          *
56717          * is equivalent to
56718          *
56719          *     dockedItems: [{
56720          *         xtype: 'toolbar',
56721          *         dock: 'top',
56722          *         items: [
56723          *             { xtype: 'button', text: 'Button 1' }
56724          *         ]
56725          *     }]
56726          */
56727         if (me.tbar) {
56728             docked.push(initToolbar(me.tbar, 'top'));
56729             me.tbar = null;
56730         }
56731
56732         /**
56733          * @cfg {Object/Object[]} bbar
56734          * Convenience config. Short for 'Bottom Bar'.
56735          *
56736          *     bbar: [
56737          *       { xtype: 'button', text: 'Button 1' }
56738          *     ]
56739          *
56740          * is equivalent to
56741          *
56742          *     dockedItems: [{
56743          *         xtype: 'toolbar',
56744          *         dock: 'bottom',
56745          *         items: [
56746          *             { xtype: 'button', text: 'Button 1' }
56747          *         ]
56748          *     }]
56749          */
56750         if (me.bbar) {
56751             docked.push(initToolbar(me.bbar, 'bottom'));
56752             me.bbar = null;
56753         }
56754
56755         /**
56756          * @cfg {Object/Object[]} buttons
56757          * Convenience config used for adding buttons docked to the bottom of the panel. This is a
56758          * synonym for the {@link #fbar} config.
56759          *
56760          *     buttons: [
56761          *       { text: 'Button 1' }
56762          *     ]
56763          *
56764          * is equivalent to
56765          *
56766          *     dockedItems: [{
56767          *         xtype: 'toolbar',
56768          *         dock: 'bottom',
56769          *         ui: 'footer',
56770          *         defaults: {minWidth: {@link #minButtonWidth}},
56771          *         items: [
56772          *             { xtype: 'component', flex: 1 },
56773          *             { xtype: 'button', text: 'Button 1' }
56774          *         ]
56775          *     }]
56776          *
56777          * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
56778          * each of the buttons in the buttons toolbar.
56779          */
56780         if (me.buttons) {
56781             me.fbar = me.buttons;
56782             me.buttons = null;
56783         }
56784
56785         /**
56786          * @cfg {Object/Object[]} fbar
56787          * Convenience config used for adding items to the bottom of the panel. Short for Footer Bar.
56788          *
56789          *     fbar: [
56790          *       { type: 'button', text: 'Button 1' }
56791          *     ]
56792          *
56793          * is equivalent to
56794          *
56795          *     dockedItems: [{
56796          *         xtype: 'toolbar',
56797          *         dock: 'bottom',
56798          *         ui: 'footer',
56799          *         defaults: {minWidth: {@link #minButtonWidth}},
56800          *         items: [
56801          *             { xtype: 'component', flex: 1 },
56802          *             { xtype: 'button', text: 'Button 1' }
56803          *         ]
56804          *     }]
56805          *
56806          * The {@link #minButtonWidth} is used as the default {@link Ext.button.Button#minWidth minWidth} for
56807          * each of the buttons in the fbar.
56808          */
56809         if (me.fbar) {
56810             fbar = initToolbar(me.fbar, 'bottom', true); // only we useButtonAlign
56811             fbar.ui = 'footer';
56812
56813             // Apply the minButtonWidth config to buttons in the toolbar
56814             if (minButtonWidth) {
56815                 fbarDefaults = fbar.defaults;
56816                 fbar.defaults = function(config) {
56817                     var defaults = fbarDefaults || {};
56818                     if ((!config.xtype || config.xtype === 'button' || (config.isComponent && config.isXType('button'))) &&
56819                             !('minWidth' in defaults)) {
56820                         defaults = Ext.apply({minWidth: minButtonWidth}, defaults);
56821                     }
56822                     return defaults;
56823                 };
56824             }
56825
56826             docked.push(fbar);
56827             me.fbar = null;
56828         }
56829
56830         /**
56831          * @cfg {Object/Object[]} lbar
56832          * Convenience config. Short for 'Left Bar' (left-docked, vertical toolbar).
56833          *
56834          *     lbar: [
56835          *       { xtype: 'button', text: 'Button 1' }
56836          *     ]
56837          *
56838          * is equivalent to
56839          *
56840          *     dockedItems: [{
56841          *         xtype: 'toolbar',
56842          *         dock: 'left',
56843          *         items: [
56844          *             { xtype: 'button', text: 'Button 1' }
56845          *         ]
56846          *     }]
56847          */
56848         if (me.lbar) {
56849             docked.push(initToolbar(me.lbar, 'left'));
56850             me.lbar = null;
56851         }
56852
56853         /**
56854          * @cfg {Object/Object[]} rbar
56855          * Convenience config. Short for 'Right Bar' (right-docked, vertical toolbar).
56856          *
56857          *     rbar: [
56858          *       { xtype: 'button', text: 'Button 1' }
56859          *     ]
56860          *
56861          * is equivalent to
56862          *
56863          *     dockedItems: [{
56864          *         xtype: 'toolbar',
56865          *         dock: 'right',
56866          *         items: [
56867          *             { xtype: 'button', text: 'Button 1' }
56868          *         ]
56869          *     }]
56870          */
56871         if (me.rbar) {
56872             docked.push(initToolbar(me.rbar, 'right'));
56873             me.rbar = null;
56874         }
56875
56876         if (me.dockedItems) {
56877             if (!Ext.isArray(me.dockedItems)) {
56878                 me.dockedItems = [me.dockedItems];
56879             }
56880             me.dockedItems = me.dockedItems.concat(docked);
56881         } else {
56882             me.dockedItems = docked;
56883         }
56884     },
56885
56886     /**
56887      * @private
56888      * Tools are a Panel-specific capabilty.
56889      * Panel uses initTools. Subclasses may contribute tools by implementing addTools.
56890      */
56891     initTools: function() {
56892         var me = this;
56893
56894         me.tools = me.tools ? Ext.Array.clone(me.tools) : [];
56895
56896         // Add a collapse tool unless configured to not show a collapse tool
56897         // or to not even show a header.
56898         if (me.collapsible && !(me.hideCollapseTool || me.header === false)) {
56899             me.collapseDirection = me.collapseDirection || me.headerPosition || 'top';
56900             me.collapseTool = me.expandTool = me.createComponent({
56901                 xtype: 'tool',
56902                 type: 'collapse-' + me.collapseDirection,
56903                 expandType: me.getOppositeDirection(me.collapseDirection),
56904                 handler: me.toggleCollapse,
56905                 scope: me
56906             });
56907
56908             // Prepend collapse tool is configured to do so.
56909             if (me.collapseFirst) {
56910                 me.tools.unshift(me.collapseTool);
56911             }
56912         }
56913
56914         // Add subclass-specific tools.
56915         me.addTools();
56916
56917         // Make Panel closable.
56918         if (me.closable) {
56919             me.addClsWithUI('closable');
56920             me.addTool({
56921                 type: 'close',
56922                 handler: Ext.Function.bind(me.close, this, [])
56923             });
56924         }
56925
56926         // Append collapse tool if needed.
56927         if (me.collapseTool && !me.collapseFirst) {
56928             me.tools.push(me.collapseTool);
56929         }
56930     },
56931
56932     /**
56933      * @private
56934      * @template
56935      * Template method to be implemented in subclasses to add their tools after the collapsible tool.
56936      */
56937     addTools: Ext.emptyFn,
56938
56939     /**
56940      * Closes the Panel. By default, this method, removes it from the DOM, {@link Ext.Component#destroy destroy}s the
56941      * Panel object and all its descendant Components. The {@link #beforeclose beforeclose} event is fired before the
56942      * close happens and will cancel the close action if it returns false.
56943      *
56944      * **Note:** This method is also affected by the {@link #closeAction} setting. For more explicit control use
56945      * {@link #destroy} and {@link #hide} methods.
56946      */
56947     close: function() {
56948         if (this.fireEvent('beforeclose', this) !== false) {
56949             this.doClose();
56950         }
56951     },
56952
56953     // private
56954     doClose: function() {
56955         this.fireEvent('close', this);
56956         this[this.closeAction]();
56957     },
56958
56959     onRender: function(ct, position) {
56960         var me = this,
56961             topContainer;
56962
56963         // Add class-specific header tools.
56964         // Panel adds collapsible and closable.
56965         me.initTools();
56966
56967         // Dock the header/title
56968         me.updateHeader();
56969
56970         // Call to super after adding the header, to prevent an unnecessary re-layout
56971         me.callParent(arguments);
56972     },
56973
56974     afterRender: function() {
56975         var me = this;
56976
56977         me.callParent(arguments);
56978
56979         // Instate the collapsed state after render. We need to wait for
56980         // this moment so that we have established at least some of our size (from our
56981         // configured dimensions or from content via the component layout)
56982         if (me.collapsed) {
56983             me.collapsed = false;
56984             me.collapse(null, false, true);
56985         }
56986     },
56987
56988     /**
56989      * Create, hide, or show the header component as appropriate based on the current config.
56990      * @private
56991      * @param {Boolean} force True to force the header to be created
56992      */
56993     updateHeader: function(force) {
56994         var me = this,
56995             header = me.header,
56996             title = me.title,
56997             tools = me.tools;
56998
56999         if (!me.preventHeader && (force || title || (tools && tools.length))) {
57000             if (!header) {
57001                 header = me.header = Ext.create('Ext.panel.Header', {
57002                     title       : title,
57003                     orientation : (me.headerPosition == 'left' || me.headerPosition == 'right') ? 'vertical' : 'horizontal',
57004                     dock        : me.headerPosition || 'top',
57005                     textCls     : me.headerTextCls,
57006                     iconCls     : me.iconCls,
57007                     baseCls     : me.baseCls + '-header',
57008                     tools       : tools,
57009                     ui          : me.ui,
57010                     indicateDrag: me.draggable,
57011                     border      : me.border,
57012                     frame       : me.frame && me.frameHeader,
57013                     ignoreParentFrame : me.frame || me.overlapHeader,
57014                     ignoreBorderManagement: me.frame || me.ignoreHeaderBorderManagement,
57015                     listeners   : me.collapsible && me.titleCollapse ? {
57016                         click: me.toggleCollapse,
57017                         scope: me
57018                     } : null
57019                 });
57020                 me.addDocked(header, 0);
57021
57022                 // Reference the Header's tool array.
57023                 // Header injects named references.
57024                 me.tools = header.tools;
57025             }
57026             header.show();
57027             me.initHeaderAria();
57028         } else if (header) {
57029             header.hide();
57030         }
57031     },
57032
57033     // inherit docs
57034     setUI: function(ui) {
57035         var me = this;
57036
57037         me.callParent(arguments);
57038
57039         if (me.header) {
57040             me.header.setUI(ui);
57041         }
57042     },
57043
57044     // private
57045     getContentTarget: function() {
57046         return this.body;
57047     },
57048
57049     getTargetEl: function() {
57050         return this.body || this.frameBody || this.el;
57051     },
57052
57053     // the overrides below allow for collapsed regions inside the border layout to be hidden
57054
57055     // inherit docs
57056     isVisible: function(deep){
57057         var me = this;
57058         if (me.collapsed && me.placeholder) {
57059             return me.placeholder.isVisible(deep);
57060         }
57061         return me.callParent(arguments);
57062     },
57063
57064     // inherit docs
57065     onHide: function(){
57066         var me = this;
57067         if (me.collapsed && me.placeholder) {
57068             me.placeholder.hide();
57069         } else {
57070             me.callParent(arguments);
57071         }
57072     },
57073
57074     // inherit docs
57075     onShow: function(){
57076         var me = this;
57077         if (me.collapsed && me.placeholder) {
57078             // force hidden back to true, since this gets set by the layout
57079             me.hidden = true;
57080             me.placeholder.show();
57081         } else {
57082             me.callParent(arguments);
57083         }
57084     },
57085
57086     addTool: function(tool) {
57087         var me = this,
57088             header = me.header;
57089
57090         if (Ext.isArray(tool)) {
57091             Ext.each(tool, me.addTool, me);
57092             return;
57093         }
57094         me.tools.push(tool);
57095         if (header) {
57096             header.addTool(tool);
57097         }
57098         me.updateHeader();
57099     },
57100
57101     getOppositeDirection: function(d) {
57102         var c = Ext.Component;
57103         switch (d) {
57104             case c.DIRECTION_TOP:
57105                 return c.DIRECTION_BOTTOM;
57106             case c.DIRECTION_RIGHT:
57107                 return c.DIRECTION_LEFT;
57108             case c.DIRECTION_BOTTOM:
57109                 return c.DIRECTION_TOP;
57110             case c.DIRECTION_LEFT:
57111                 return c.DIRECTION_RIGHT;
57112         }
57113     },
57114
57115     /**
57116      * Collapses the panel body so that the body becomes hidden. Docked Components parallel to the border towards which
57117      * the collapse takes place will remain visible. Fires the {@link #beforecollapse} event which will cancel the
57118      * collapse action if it returns false.
57119      *
57120      * @param {String} direction . The direction to collapse towards. Must be one of
57121      *
57122      *   - Ext.Component.DIRECTION_TOP
57123      *   - Ext.Component.DIRECTION_RIGHT
57124      *   - Ext.Component.DIRECTION_BOTTOM
57125      *   - Ext.Component.DIRECTION_LEFT
57126      *
57127      * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
57128      * {@link #animCollapse} panel config)
57129      * @return {Ext.panel.Panel} this
57130      */
57131     collapse: function(direction, animate, /* private - passed if called at render time */ internal) {
57132         var me = this,
57133             c = Ext.Component,
57134             height = me.getHeight(),
57135             width = me.getWidth(),
57136             frameInfo,
57137             newSize = 0,
57138             dockedItems = me.dockedItems.items,
57139             dockedItemCount = dockedItems.length,
57140             i = 0,
57141             comp,
57142             pos,
57143             anim = {
57144                 from: {
57145                     height: height,
57146                     width: width
57147                 },
57148                 to: {
57149                     height: height,
57150                     width: width
57151                 },
57152                 listeners: {
57153                     afteranimate: me.afterCollapse,
57154                     scope: me
57155                 },
57156                 duration: Ext.Number.from(animate, Ext.fx.Anim.prototype.duration)
57157             },
57158             reExpander,
57159             reExpanderOrientation,
57160             reExpanderDock,
57161             getDimension,
57162             collapseDimension;
57163
57164         if (!direction) {
57165             direction = me.collapseDirection;
57166         }
57167
57168         // If internal (Called because of initial collapsed state), then no animation, and no events.
57169         if (internal) {
57170             animate = false;
57171         } else if (me.collapsed || me.fireEvent('beforecollapse', me, direction, animate) === false) {
57172             return false;
57173         }
57174
57175         reExpanderDock = direction;
57176         me.expandDirection = me.getOppositeDirection(direction);
57177
57178         // Track docked items which we hide during collapsed state
57179         me.hiddenDocked = [];
57180
57181         switch (direction) {
57182             case c.DIRECTION_TOP:
57183             case c.DIRECTION_BOTTOM:
57184                 reExpanderOrientation = 'horizontal';
57185                 collapseDimension = 'height';
57186                 getDimension = 'getHeight';
57187
57188                 // Attempt to find a reExpander Component (docked in a horizontal orientation)
57189                 // Also, collect all other docked items which we must hide after collapse.
57190                 for (; i < dockedItemCount; i++) {
57191                     comp = dockedItems[i];
57192                     if (comp.isVisible()) {
57193                         if (comp.isXType('header', true) && (!comp.dock || comp.dock == 'top' || comp.dock == 'bottom')) {
57194                             reExpander = comp;
57195                         } else {
57196                             me.hiddenDocked.push(comp);
57197                         }
57198                     } else if (comp === me.reExpander) {
57199                         reExpander = comp;
57200                     }
57201                 }
57202
57203                 if (direction == Ext.Component.DIRECTION_BOTTOM) {
57204                     pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
57205                     anim.from.top = pos;
57206                 }
57207                 break;
57208
57209             case c.DIRECTION_LEFT:
57210             case c.DIRECTION_RIGHT:
57211                 reExpanderOrientation = 'vertical';
57212                 collapseDimension = 'width';
57213                 getDimension = 'getWidth';
57214
57215                 // Attempt to find a reExpander Component (docked in a vecrtical orientation)
57216                 // Also, collect all other docked items which we must hide after collapse.
57217                 for (; i < dockedItemCount; i++) {
57218                     comp = dockedItems[i];
57219                     if (comp.isVisible()) {
57220                         if (comp.isHeader && (comp.dock == 'left' || comp.dock == 'right')) {
57221                             reExpander = comp;
57222                         } else {
57223                             me.hiddenDocked.push(comp);
57224                         }
57225                     } else if (comp === me.reExpander) {
57226                         reExpander = comp;
57227                     }
57228                 }
57229
57230                 if (direction == Ext.Component.DIRECTION_RIGHT) {
57231                     pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
57232                     anim.from.left = pos;
57233                 }
57234                 break;
57235
57236             default:
57237                 throw('Panel collapse must be passed a valid Component collapse direction');
57238         }
57239
57240         // Disable toggle tool during animated collapse
57241         if (animate && me.collapseTool) {
57242             me.collapseTool.disable();
57243         }
57244
57245         // Add the collapsed class now, so that collapsed CSS rules are applied before measurements are taken.
57246         me.addClsWithUI(me.collapsedCls);
57247         // if (me.border === false) {
57248         //     me.addClsWithUI(me.collapsedCls + '-noborder');
57249         // }
57250
57251         // We found a header: Measure it to find the collapse-to size.
57252         if (reExpander && reExpander.rendered) {
57253
57254             //we must add the collapsed cls to the header and then remove to get the proper height
57255             reExpander.addClsWithUI(me.collapsedCls);
57256             reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57257             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57258                 reExpander.addClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
57259             }
57260
57261             frameInfo = reExpander.getFrameInfo();
57262
57263             //get the size
57264             newSize = reExpander[getDimension]() + (frameInfo ? frameInfo[direction] : 0);
57265
57266             //and remove
57267             reExpander.removeClsWithUI(me.collapsedCls);
57268             reExpander.removeClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57269             if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57270                 reExpander.removeClsWithUI(me.collapsedCls + '-border-' + reExpander.dock);
57271             }
57272         }
57273         // No header: Render and insert a temporary one, and then measure it.
57274         else {
57275             reExpander = {
57276                 hideMode: 'offsets',
57277                 temporary: true,
57278                 title: me.title,
57279                 orientation: reExpanderOrientation,
57280                 dock: reExpanderDock,
57281                 textCls: me.headerTextCls,
57282                 iconCls: me.iconCls,
57283                 baseCls: me.baseCls + '-header',
57284                 ui: me.ui,
57285                 frame: me.frame && me.frameHeader,
57286                 ignoreParentFrame: me.frame || me.overlapHeader,
57287                 indicateDrag: me.draggable,
57288                 cls: me.baseCls + '-collapsed-placeholder ' + ' ' + Ext.baseCSSPrefix + 'docked ' + me.baseCls + '-' + me.ui + '-collapsed',
57289                 renderTo: me.el
57290             };
57291             if (!me.hideCollapseTool) {
57292                 reExpander[(reExpander.orientation == 'horizontal') ? 'tools' : 'items'] = [{
57293                     xtype: 'tool',
57294                     type: 'expand-' + me.expandDirection,
57295                     handler: me.toggleCollapse,
57296                     scope: me
57297                 }];
57298             }
57299
57300             // Capture the size of the re-expander.
57301             // For vertical headers in IE6 and IE7, this will be sized by a CSS rule in _panel.scss
57302             reExpander = me.reExpander = Ext.create('Ext.panel.Header', reExpander);
57303             newSize = reExpander[getDimension]() + ((reExpander.frame) ? reExpander.frameSize[direction] : 0);
57304             reExpander.hide();
57305
57306             // Insert the new docked item
57307             me.insertDocked(0, reExpander);
57308         }
57309
57310         me.reExpander = reExpander;
57311         me.reExpander.addClsWithUI(me.collapsedCls);
57312         me.reExpander.addClsWithUI(me.collapsedCls + '-' + reExpander.dock);
57313         if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57314             me.reExpander.addClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
57315         }
57316
57317         // If collapsing right or down, we'll be also animating the left or top.
57318         if (direction == Ext.Component.DIRECTION_RIGHT) {
57319             anim.to.left = pos + (width - newSize);
57320         } else if (direction == Ext.Component.DIRECTION_BOTTOM) {
57321             anim.to.top = pos + (height - newSize);
57322         }
57323
57324         // Animate to the new size
57325         anim.to[collapseDimension] = newSize;
57326
57327         // When we collapse a panel, the panel is in control of one dimension (depending on
57328         // collapse direction) and sets that on the component. We must restore the user's
57329         // original value (including non-existance) when we expand. Using this technique, we
57330         // mimic setCalculatedSize for the dimension we do not control and setSize for the
57331         // one we do (only while collapsed).
57332         if (!me.collapseMemento) {
57333             me.collapseMemento = new Ext.util.Memento(me);
57334         }
57335         me.collapseMemento.capture(['width', 'height', 'minWidth', 'minHeight', 'layoutManagedHeight', 'layoutManagedWidth']);
57336
57337         // Remove any flex config before we attempt to collapse.
57338         me.savedFlex = me.flex;
57339         me.minWidth = 0;
57340         me.minHeight = 0;
57341         delete me.flex;
57342         me.suspendLayout = true;
57343
57344         if (animate) {
57345             me.animate(anim);
57346         } else {
57347             me.setSize(anim.to.width, anim.to.height);
57348             if (Ext.isDefined(anim.to.left) || Ext.isDefined(anim.to.top)) {
57349                 me.setPosition(anim.to.left, anim.to.top);
57350             }
57351             me.afterCollapse(false, internal);
57352         }
57353         return me;
57354     },
57355
57356     afterCollapse: function(animated, internal) {
57357         var me = this,
57358             i = 0,
57359             l = me.hiddenDocked.length;
57360
57361         me.collapseMemento.restore(['minWidth', 'minHeight']);
57362
57363         // Now we can restore the dimension we don't control to its original state
57364         // Leave the value in the memento so that it can be correctly restored
57365         // if it is set by animation.
57366         if (Ext.Component.VERTICAL_DIRECTION_Re.test(me.expandDirection)) {
57367             me.layoutManagedHeight = 2;
57368             me.collapseMemento.restore('width', false);
57369         } else {
57370             me.layoutManagedWidth = 2;
57371             me.collapseMemento.restore('height', false);
57372         }
57373
57374         // We must hide the body, otherwise it overlays docked items which come before
57375         // it in the DOM order. Collapsing its dimension won't work - padding and borders keep a size.
57376         me.saveScrollTop = me.body.dom.scrollTop;
57377         me.body.setStyle('display', 'none');
57378
57379         for (; i < l; i++) {
57380             me.hiddenDocked[i].hide();
57381         }
57382         if (me.reExpander) {
57383             me.reExpander.updateFrame();
57384             me.reExpander.show();
57385         }
57386         me.collapsed = true;
57387         me.suspendLayout = false;
57388
57389         if (!internal) {
57390             if (me.ownerCt) {
57391                 // Because Component layouts only inform upstream containers if they have changed size,
57392                 // explicitly lay out the container now, because the lastComponentsize will have been set by the non-animated setCalculatedSize.
57393                 if (animated) {
57394                     me.ownerCt.layout.layout();
57395                 }
57396             } else if (me.reExpander.temporary) {
57397                 me.doComponentLayout();
57398             }
57399         }
57400
57401         if (me.resizer) {
57402             me.resizer.disable();
57403         }
57404
57405         // If me Panel was configured with a collapse tool in its header, flip it's type
57406         if (me.collapseTool) {
57407             me.collapseTool.setType('expand-' + me.expandDirection);
57408         }
57409         if (!internal) {
57410             me.fireEvent('collapse', me);
57411         }
57412
57413         // Re-enable the toggle tool after an animated collapse
57414         if (animated && me.collapseTool) {
57415             me.collapseTool.enable();
57416         }
57417     },
57418
57419     /**
57420      * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will cancel the
57421      * expand action if it returns false.
57422      * @param {Boolean} [animate] True to animate the transition, else false (defaults to the value of the
57423      * {@link #animCollapse} panel config)
57424      * @return {Ext.panel.Panel} this
57425      */
57426     expand: function(animate) {
57427         var me = this;
57428         if (!me.collapsed || me.fireEvent('beforeexpand', me, animate) === false) {
57429             return false;
57430         }
57431
57432         var i = 0,
57433             l = me.hiddenDocked.length,
57434             direction = me.expandDirection,
57435             height = me.getHeight(),
57436             width = me.getWidth(),
57437             pos, anim;
57438
57439         // Disable toggle tool during animated expand
57440         if (animate && me.collapseTool) {
57441             me.collapseTool.disable();
57442         }
57443
57444         // Show any docked items that we hid on collapse
57445         // And hide the injected reExpander Header
57446         for (; i < l; i++) {
57447             me.hiddenDocked[i].hidden = false;
57448             me.hiddenDocked[i].el.show();
57449         }
57450         if (me.reExpander) {
57451             if (me.reExpander.temporary) {
57452                 me.reExpander.hide();
57453             } else {
57454                 me.reExpander.removeClsWithUI(me.collapsedCls);
57455                 me.reExpander.removeClsWithUI(me.collapsedCls + '-' + me.reExpander.dock);
57456                 if (me.border && (!me.frame || (me.frame && Ext.supports.CSS3BorderRadius))) {
57457                     me.reExpander.removeClsWithUI(me.collapsedCls + '-border-' + me.reExpander.dock);
57458                 }
57459                 me.reExpander.updateFrame();
57460             }
57461         }
57462
57463         // If me Panel was configured with a collapse tool in its header, flip it's type
57464         if (me.collapseTool) {
57465             me.collapseTool.setType('collapse-' + me.collapseDirection);
57466         }
57467
57468         // Restore body display and scroll position
57469         me.body.setStyle('display', '');
57470         me.body.dom.scrollTop = me.saveScrollTop;
57471
57472         // Unset the flag before the potential call to calculateChildBox to calculate our newly flexed size
57473         me.collapsed = false;
57474
57475         // Remove any collapsed styling before any animation begins
57476         me.removeClsWithUI(me.collapsedCls);
57477         // if (me.border === false) {
57478         //     me.removeClsWithUI(me.collapsedCls + '-noborder');
57479         // }
57480
57481         anim = {
57482             to: {
57483             },
57484             from: {
57485                 height: height,
57486                 width: width
57487             },
57488             listeners: {
57489                 afteranimate: me.afterExpand,
57490                 scope: me
57491             }
57492         };
57493
57494         if ((direction == Ext.Component.DIRECTION_TOP) || (direction == Ext.Component.DIRECTION_BOTTOM)) {
57495
57496             // Restore the collapsed dimension.
57497             // Leave it in the memento, so that the final restoreAll can overwrite anything that animation does.
57498             me.collapseMemento.restore('height', false);
57499
57500             // If autoHeight, measure the height now we have shown the body element.
57501             if (me.height === undefined) {
57502                 me.setCalculatedSize(me.width, null);
57503                 anim.to.height = me.getHeight();
57504
57505                 // Must size back down to collapsed for the animation.
57506                 me.setCalculatedSize(me.width, anim.from.height);
57507             }
57508             // If we were flexed, then we can't just restore to the saved size.
57509             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
57510             else if (me.savedFlex) {
57511                 me.flex = me.savedFlex;
57512                 anim.to.height = me.ownerCt.layout.calculateChildBox(me).height;
57513                 delete me.flex;
57514             }
57515             // Else, restore to saved height
57516             else {
57517                 anim.to.height = me.height;
57518             }
57519
57520             // top needs animating upwards
57521             if (direction == Ext.Component.DIRECTION_TOP) {
57522                 pos = me.getPosition()[1] - Ext.fly(me.el.dom.offsetParent).getRegion().top;
57523                 anim.from.top = pos;
57524                 anim.to.top = pos - (anim.to.height - height);
57525             }
57526         } else if ((direction == Ext.Component.DIRECTION_LEFT) || (direction == Ext.Component.DIRECTION_RIGHT)) {
57527
57528             // Restore the collapsed dimension.
57529             // Leave it in the memento, so that the final restoreAll can overwrite anything that animation does.
57530             me.collapseMemento.restore('width', false);
57531
57532             // If autoWidth, measure the width now we have shown the body element.
57533             if (me.width === undefined) {
57534                 me.setCalculatedSize(null, me.height);
57535                 anim.to.width = me.getWidth();
57536
57537                 // Must size back down to collapsed for the animation.
57538                 me.setCalculatedSize(anim.from.width, me.height);
57539             }
57540             // If we were flexed, then we can't just restore to the saved size.
57541             // We must restore to the currently correct, flexed size, so we much ask the Box layout what that is.
57542             else if (me.savedFlex) {
57543                 me.flex = me.savedFlex;
57544                 anim.to.width = me.ownerCt.layout.calculateChildBox(me).width;
57545                 delete me.flex;
57546             }
57547             // Else, restore to saved width
57548             else {
57549                 anim.to.width = me.width;
57550             }
57551
57552             // left needs animating leftwards
57553             if (direction == Ext.Component.DIRECTION_LEFT) {
57554                 pos = me.getPosition()[0] - Ext.fly(me.el.dom.offsetParent).getRegion().left;
57555                 anim.from.left = pos;
57556                 anim.to.left = pos - (anim.to.width - width);
57557             }
57558         }
57559
57560         if (animate) {
57561             me.animate(anim);
57562         } else {
57563             me.setCalculatedSize(anim.to.width, anim.to.height);
57564             if (anim.to.x) {
57565                 me.setLeft(anim.to.x);
57566             }
57567             if (anim.to.y) {
57568                 me.setTop(anim.to.y);
57569             }
57570             me.afterExpand(false);
57571         }
57572
57573         return me;
57574     },
57575
57576     afterExpand: function(animated) {
57577         var me = this;
57578
57579         // Restored to a calculated flex. Delete the set width and height properties so that flex works from now on.
57580         if (me.savedFlex) {
57581             me.flex = me.savedFlex;
57582             delete me.savedFlex;
57583             delete me.width;
57584             delete me.height;
57585         }
57586
57587         // Restore width/height and dimension management flags to original values
57588         if (me.collapseMemento) {
57589             me.collapseMemento.restoreAll();
57590         }
57591
57592         if (animated && me.ownerCt) {
57593             // IE 6 has an intermittent repaint issue in this case so give
57594             // it a little extra time to catch up before laying out.
57595             Ext.defer(me.ownerCt.doLayout, Ext.isIE6 ? 1 : 0, me);
57596         }
57597
57598         if (me.resizer) {
57599             me.resizer.enable();
57600         }
57601
57602         me.fireEvent('expand', me);
57603
57604         // Re-enable the toggle tool after an animated expand
57605         if (animated && me.collapseTool) {
57606             me.collapseTool.enable();
57607         }
57608     },
57609
57610     /**
57611      * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel.
57612      * @return {Ext.panel.Panel} this
57613      */
57614     toggleCollapse: function() {
57615         if (this.collapsed) {
57616             this.expand(this.animCollapse);
57617         } else {
57618             this.collapse(this.collapseDirection, this.animCollapse);
57619         }
57620         return this;
57621     },
57622
57623     // private
57624     getKeyMap : function(){
57625         if(!this.keyMap){
57626             this.keyMap = Ext.create('Ext.util.KeyMap', this.el, this.keys);
57627         }
57628         return this.keyMap;
57629     },
57630
57631     // private
57632     initDraggable : function(){
57633         /**
57634          * @property {Ext.dd.DragSource} dd
57635          * If this Panel is configured {@link #draggable}, this property will contain an instance of {@link
57636          * Ext.dd.DragSource} which handles dragging the Panel.
57637          *
57638          * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource} in order to
57639          * supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
57640          */
57641         this.dd = Ext.create('Ext.panel.DD', this, Ext.isBoolean(this.draggable) ? null : this.draggable);
57642     },
57643
57644     // private - helper function for ghost
57645     ghostTools : function() {
57646         var tools = [],
57647             headerTools = this.header.query('tool[hidden=false]');
57648
57649         if (headerTools.length) {
57650             Ext.each(headerTools, function(tool) {
57651                 // Some tools can be full components, and copying them into the ghost
57652                 // actually removes them from the owning panel. You could also potentially
57653                 // end up with duplicate DOM ids as well. To avoid any issues we just make
57654                 // a simple bare-minimum clone of each tool for ghosting purposes.
57655                 tools.push({
57656                     type: tool.type
57657                 });
57658             });
57659         } else {
57660             tools = [{
57661                 type: 'placeholder'
57662             }];
57663         }
57664         return tools;
57665     },
57666
57667     // private - used for dragging
57668     ghost: function(cls) {
57669         var me = this,
57670             ghostPanel = me.ghostPanel,
57671             box = me.getBox(),
57672             header;
57673
57674         if (!ghostPanel) {
57675             ghostPanel = Ext.create('Ext.panel.Panel', {
57676                 renderTo: me.floating ? me.el.dom.parentNode : document.body,
57677                 floating: {
57678                     shadow: false
57679                 },
57680                 frame: Ext.supports.CSS3BorderRadius ? me.frame : false,
57681                 overlapHeader: me.overlapHeader,
57682                 headerPosition: me.headerPosition,
57683                 baseCls: me.baseCls,
57684                 cls: me.baseCls + '-ghost ' + (cls ||'')
57685             });
57686             me.ghostPanel = ghostPanel;
57687         }
57688         ghostPanel.floatParent = me.floatParent;
57689         if (me.floating) {
57690             ghostPanel.setZIndex(Ext.Number.from(me.el.getStyle('zIndex'), 0));
57691         } else {
57692             ghostPanel.toFront();
57693         }
57694         header = ghostPanel.header;
57695         // restore options
57696         if (header) {
57697             header.suspendLayout = true;
57698             Ext.Array.forEach(header.query('tool'), function(tool){
57699                 header.remove(tool);
57700             });
57701             header.suspendLayout = false;
57702         }
57703         ghostPanel.addTool(me.ghostTools());
57704         ghostPanel.setTitle(me.title);
57705         ghostPanel.setIconCls(me.iconCls);
57706
57707         ghostPanel.el.show();
57708         ghostPanel.setPosition(box.x, box.y);
57709         ghostPanel.setSize(box.width, box.height);
57710         me.el.hide();
57711         if (me.floatingItems) {
57712             me.floatingItems.hide();
57713         }
57714         return ghostPanel;
57715     },
57716
57717     // private
57718     unghost: function(show, matchPosition) {
57719         var me = this;
57720         if (!me.ghostPanel) {
57721             return;
57722         }
57723         if (show !== false) {
57724             me.el.show();
57725             if (matchPosition !== false) {
57726                 me.setPosition(me.ghostPanel.getPosition());
57727             }
57728             if (me.floatingItems) {
57729                 me.floatingItems.show();
57730             }
57731             Ext.defer(me.focus, 10, me);
57732         }
57733         me.ghostPanel.el.hide();
57734     },
57735
57736     initResizable: function(resizable) {
57737         if (this.collapsed) {
57738             resizable.disabled = true;
57739         }
57740         this.callParent([resizable]);
57741     }
57742 }, function(){
57743     this.prototype.animCollapse = Ext.enableFx;
57744 });
57745
57746 /**
57747  * Component layout for Tip/ToolTip/etc. components
57748  * @class Ext.layout.component.Tip
57749  * @extends Ext.layout.component.Dock
57750  * @private
57751  */
57752
57753 Ext.define('Ext.layout.component.Tip', {
57754
57755     /* Begin Definitions */
57756
57757     alias: ['layout.tip'],
57758
57759     extend: 'Ext.layout.component.Dock',
57760
57761     /* End Definitions */
57762
57763     type: 'tip',
57764     
57765     onLayout: function(width, height) {
57766         var me = this,
57767             owner = me.owner,
57768             el = owner.el,
57769             minWidth,
57770             maxWidth,
57771             naturalWidth,
57772             constrainedWidth,
57773             xy = el.getXY();
57774
57775         // Position offscreen so the natural width is not affected by the viewport's right edge
57776         el.setXY([-9999,-9999]);
57777
57778         // Calculate initial layout
57779         this.callParent(arguments);
57780
57781         // Handle min/maxWidth for auto-width tips
57782         if (!Ext.isNumber(width)) {
57783             minWidth = owner.minWidth;
57784             maxWidth = owner.maxWidth;
57785             // IE6/7 in strict mode have a problem doing an autoWidth
57786             if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
57787                 constrainedWidth = me.doAutoWidth();
57788             } else {
57789                 naturalWidth = el.getWidth();
57790             }
57791             if (naturalWidth < minWidth) {
57792                 constrainedWidth = minWidth;
57793             }
57794             else if (naturalWidth > maxWidth) {
57795                 constrainedWidth = maxWidth;
57796             }
57797             if (constrainedWidth) {
57798                 this.callParent([constrainedWidth, height]);
57799             }
57800         }
57801
57802         // Restore position
57803         el.setXY(xy);
57804     },
57805     
57806     doAutoWidth: function(){
57807         var me = this,
57808             owner = me.owner,
57809             body = owner.body,
57810             width = body.getTextWidth();
57811             
57812         if (owner.header) {
57813             width = Math.max(width, owner.header.getWidth());
57814         }
57815         if (!Ext.isDefined(me.frameWidth)) {
57816             me.frameWidth = owner.el.getWidth() - body.getWidth();
57817         }
57818         width += me.frameWidth + body.getPadding('lr');
57819         return width;
57820     }
57821 });
57822
57823 /**
57824  * @class Ext.tip.Tip
57825  * @extends Ext.panel.Panel
57826  * This is the base class for {@link Ext.tip.QuickTip} and {@link Ext.tip.ToolTip} that provides the basic layout and
57827  * positioning that all tip-based classes require. This class can be used directly for simple, statically-positioned
57828  * tips that are displayed programmatically, or it can be extended to provide custom tip implementations.
57829  * @xtype tip
57830  */
57831 Ext.define('Ext.tip.Tip', {
57832     extend: 'Ext.panel.Panel',
57833     requires: [ 'Ext.layout.component.Tip' ],
57834     alternateClassName: 'Ext.Tip',
57835     /**
57836      * @cfg {Boolean} [closable=false]
57837      * True to render a close tool button into the tooltip header.
57838      */
57839     /**
57840      * @cfg {Number} width
57841      * Width in pixels of the tip (defaults to auto).  Width will be ignored if it exceeds the bounds of
57842      * {@link #minWidth} or {@link #maxWidth}.  The maximum supported value is 500.
57843      */
57844     /**
57845      * @cfg {Number} minWidth The minimum width of the tip in pixels.
57846      */
57847     minWidth : 40,
57848     /**
57849      * @cfg {Number} maxWidth The maximum width of the tip in pixels.  The maximum supported value is 500.
57850      */
57851     maxWidth : 300,
57852     /**
57853      * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
57854      * for bottom-right shadow.
57855      */
57856     shadow : "sides",
57857
57858     /**
57859      * @cfg {String} defaultAlign
57860      * <b>Experimental</b>. The default {@link Ext.Element#alignTo} anchor position value for this tip relative
57861      * to its element of origin.
57862      */
57863     defaultAlign : "tl-bl?",
57864     /**
57865      * @cfg {Boolean} constrainPosition
57866      * If true, then the tooltip will be automatically constrained to stay within the browser viewport.
57867      */
57868     constrainPosition : true,
57869
57870     // @inherited
57871     frame: false,
57872
57873     // private panel overrides
57874     autoRender: true,
57875     hidden: true,
57876     baseCls: Ext.baseCSSPrefix + 'tip',
57877     floating: {
57878         shadow: true,
57879         shim: true,
57880         constrain: true
57881     },
57882     focusOnToFront: false,
57883     componentLayout: 'tip',
57884
57885     /**
57886      * @cfg {String} closeAction
57887      * <p>The action to take when the close header tool is clicked:
57888      * <div class="mdetail-params"><ul>
57889      * <li><b><code>'{@link #destroy}'</code></b> : <div class="sub-desc">
57890      * {@link #destroy remove} the window from the DOM and {@link Ext.Component#destroy destroy}
57891      * it and all descendant Components. The window will <b>not</b> be available to be
57892      * redisplayed via the {@link #show} method.
57893      * </div></li>
57894      * <li><b><code>'{@link #hide}'</code></b> : <b>Default</b><div class="sub-desc">
57895      * {@link #hide} the window by setting visibility to hidden and applying negative offsets.
57896      * The window will be available to be redisplayed via the {@link #show} method.
57897      * </div></li>
57898      * </ul></div>
57899      * <p><b>Note:</b> This behavior has changed! setting *does* affect the {@link #close} method
57900      * which will invoke the approriate closeAction.
57901      */
57902     closeAction: 'hide',
57903
57904     ariaRole: 'tooltip',
57905
57906     initComponent: function() {
57907         var me = this;
57908
57909         me.floating = Ext.apply({}, {shadow: me.shadow}, me.self.prototype.floating);
57910         me.callParent(arguments);
57911
57912         // Or in the deprecated config. Floating.doConstrain only constrains if the constrain property is truthy.
57913         me.constrain = me.constrain || me.constrainPosition;
57914     },
57915
57916     /**
57917      * Shows this tip at the specified XY position.  Example usage:
57918      * <pre><code>
57919 // Show the tip at x:50 and y:100
57920 tip.showAt([50,100]);
57921 </code></pre>
57922      * @param {Number[]} xy An array containing the x and y coordinates
57923      */
57924     showAt : function(xy){
57925         var me = this;
57926         this.callParent(arguments);
57927         // Show may have been vetoed.
57928         if (me.isVisible()) {
57929             me.setPagePosition(xy[0], xy[1]);
57930             if (me.constrainPosition || me.constrain) {
57931                 me.doConstrain();
57932             }
57933             me.toFront(true);
57934         }
57935     },
57936
57937     /**
57938      * <b>Experimental</b>. Shows this tip at a position relative to another element using a standard {@link Ext.Element#alignTo}
57939      * anchor position value.  Example usage:
57940      * <pre><code>
57941 // Show the tip at the default position ('tl-br?')
57942 tip.showBy('my-el');
57943
57944 // Show the tip's top-left corner anchored to the element's top-right corner
57945 tip.showBy('my-el', 'tl-tr');
57946 </code></pre>
57947      * @param {String/HTMLElement/Ext.Element} el An HTMLElement, Ext.Element or string id of the target element to align to
57948      * @param {String} [position] A valid {@link Ext.Element#alignTo} anchor position (defaults to 'tl-br?' or
57949      * {@link #defaultAlign} if specified).
57950      */
57951     showBy : function(el, pos) {
57952         this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign));
57953     },
57954
57955     /**
57956      * @private
57957      * @override
57958      * Set Tip draggable using base Component's draggability
57959      */
57960     initDraggable : function(){
57961         var me = this;
57962         me.draggable = {
57963             el: me.getDragEl(),
57964             delegate: me.header.el,
57965             constrain: me,
57966             constrainTo: me.el.getScopeParent()
57967         };
57968         // Important: Bypass Panel's initDraggable. Call direct to Component's implementation.
57969         Ext.Component.prototype.initDraggable.call(me);
57970     },
57971
57972     // Tip does not ghost. Drag is "live"
57973     ghost: undefined,
57974     unghost: undefined
57975 });
57976
57977 /**
57978  * ToolTip is a {@link Ext.tip.Tip} implementation that handles the common case of displaying a
57979  * tooltip when hovering over a certain element or elements on the page. It allows fine-grained
57980  * control over the tooltip's alignment relative to the target element or mouse, and the timing
57981  * of when it is automatically shown and hidden.
57982  *
57983  * This implementation does **not** have a built-in method of automatically populating the tooltip's
57984  * text based on the target element; you must either configure a fixed {@link #html} value for each
57985  * ToolTip instance, or implement custom logic (e.g. in a {@link #beforeshow} event listener) to
57986  * generate the appropriate tooltip content on the fly. See {@link Ext.tip.QuickTip} for a more
57987  * convenient way of automatically populating and configuring a tooltip based on specific DOM
57988  * attributes of each target element.
57989  *
57990  * # Basic Example
57991  *
57992  *     var tip = Ext.create('Ext.tip.ToolTip', {
57993  *         target: 'clearButton',
57994  *         html: 'Press this button to clear the form'
57995  *     });
57996  *
57997  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip1.png Basic Ext.tip.ToolTip}
57998  *
57999  * # Delegation
58000  *
58001  * In addition to attaching a ToolTip to a single element, you can also use delegation to attach
58002  * one ToolTip to many elements under a common parent. This is more efficient than creating many
58003  * ToolTip instances. To do this, point the {@link #target} config to a common ancestor of all the
58004  * elements, and then set the {@link #delegate} config to a CSS selector that will select all the
58005  * appropriate sub-elements.
58006  *
58007  * When using delegation, it is likely that you will want to programmatically change the content
58008  * of the ToolTip based on each delegate element; you can do this by implementing a custom
58009  * listener for the {@link #beforeshow} event. Example:
58010  *
58011  *     var store = Ext.create('Ext.data.ArrayStore', {
58012  *         fields: ['company', 'price', 'change'],
58013  *         data: [
58014  *             ['3m Co',                               71.72, 0.02],
58015  *             ['Alcoa Inc',                           29.01, 0.42],
58016  *             ['Altria Group Inc',                    83.81, 0.28],
58017  *             ['American Express Company',            52.55, 0.01],
58018  *             ['American International Group, Inc.',  64.13, 0.31],
58019  *             ['AT&T Inc.',                           31.61, -0.48]
58020  *         ]
58021  *     });
58022  *
58023  *     var grid = Ext.create('Ext.grid.Panel', {
58024  *         title: 'Array Grid',
58025  *         store: store,
58026  *         columns: [
58027  *             {text: 'Company', flex: 1, dataIndex: 'company'},
58028  *             {text: 'Price', width: 75, dataIndex: 'price'},
58029  *             {text: 'Change', width: 75, dataIndex: 'change'}
58030  *         ],
58031  *         height: 200,
58032  *         width: 400,
58033  *         renderTo: Ext.getBody()
58034  *     });
58035  *
58036  *     grid.getView().on('render', function(view) {
58037  *         view.tip = Ext.create('Ext.tip.ToolTip', {
58038  *             // The overall target element.
58039  *             target: view.el,
58040  *             // Each grid row causes its own seperate show and hide.
58041  *             delegate: view.itemSelector,
58042  *             // Moving within the row should not hide the tip.
58043  *             trackMouse: true,
58044  *             // Render immediately so that tip.body can be referenced prior to the first show.
58045  *             renderTo: Ext.getBody(),
58046  *             listeners: {
58047  *                 // Change content dynamically depending on which element triggered the show.
58048  *                 beforeshow: function updateTipBody(tip) {
58049  *                     tip.update('Over company "' + view.getRecord(tip.triggerElement).get('company') + '"');
58050  *                 }
58051  *             }
58052  *         });
58053  *     });
58054  *
58055  * {@img Ext.tip.ToolTip/Ext.tip.ToolTip2.png Ext.tip.ToolTip with delegation}
58056  *
58057  * # Alignment
58058  *
58059  * The following configuration properties allow control over how the ToolTip is aligned relative to
58060  * the target element and/or mouse pointer:
58061  *
58062  * - {@link #anchor}
58063  * - {@link #anchorToTarget}
58064  * - {@link #anchorOffset}
58065  * - {@link #trackMouse}
58066  * - {@link #mouseOffset}
58067  *
58068  * # Showing/Hiding
58069  *
58070  * The following configuration properties allow control over how and when the ToolTip is automatically
58071  * shown and hidden:
58072  *
58073  * - {@link #autoHide}
58074  * - {@link #showDelay}
58075  * - {@link #hideDelay}
58076  * - {@link #dismissDelay}
58077  *
58078  * @docauthor Jason Johnston <jason@sencha.com>
58079  */
58080 Ext.define('Ext.tip.ToolTip', {
58081     extend: 'Ext.tip.Tip',
58082     alias: 'widget.tooltip',
58083     alternateClassName: 'Ext.ToolTip',
58084     /**
58085      * @property {HTMLElement} triggerElement
58086      * When a ToolTip is configured with the `{@link #delegate}`
58087      * option to cause selected child elements of the `{@link #target}`
58088      * Element to each trigger a seperate show event, this property is set to
58089      * the DOM element which triggered the show.
58090      */
58091     /**
58092      * @cfg {HTMLElement/Ext.Element/String} target
58093      * The target element or string id to monitor for mouseover events to trigger
58094      * showing this ToolTip.
58095      */
58096     /**
58097      * @cfg {Boolean} [autoHide=true]
58098      * True to automatically hide the tooltip after the
58099      * mouse exits the target element or after the `{@link #dismissDelay}`
58100      * has expired if set.  If `{@link #closable} = true`
58101      * a close tool button will be rendered into the tooltip header.
58102      */
58103     /**
58104      * @cfg {Number} showDelay
58105      * Delay in milliseconds before the tooltip displays after the mouse enters the target element.
58106      */
58107     showDelay: 500,
58108     /**
58109      * @cfg {Number} hideDelay
58110      * Delay in milliseconds after the mouse exits the target element but before the tooltip actually hides.
58111      * Set to 0 for the tooltip to hide immediately.
58112      */
58113     hideDelay: 200,
58114     /**
58115      * @cfg {Number} dismissDelay
58116      * Delay in milliseconds before the tooltip automatically hides. To disable automatic hiding, set
58117      * dismissDelay = 0.
58118      */
58119     dismissDelay: 5000,
58120     /**
58121      * @cfg {Number[]} [mouseOffset=[15,18]]
58122      * An XY offset from the mouse position where the tooltip should be shown.
58123      */
58124     /**
58125      * @cfg {Boolean} trackMouse
58126      * True to have the tooltip follow the mouse as it moves over the target element.
58127      */
58128     trackMouse: false,
58129     /**
58130      * @cfg {String} anchor
58131      * If specified, indicates that the tip should be anchored to a
58132      * particular side of the target element or mouse pointer ("top", "right", "bottom",
58133      * or "left"), with an arrow pointing back at the target or mouse pointer. If
58134      * {@link #constrainPosition} is enabled, this will be used as a preferred value
58135      * only and may be flipped as needed.
58136      */
58137     /**
58138      * @cfg {Boolean} anchorToTarget
58139      * True to anchor the tooltip to the target element, false to anchor it relative to the mouse coordinates.
58140      * When `anchorToTarget` is true, use `{@link #defaultAlign}` to control tooltip alignment to the
58141      * target element.  When `anchorToTarget` is false, use `{@link #anchor}` instead to control alignment.
58142      */
58143     anchorToTarget: true,
58144     /**
58145      * @cfg {Number} anchorOffset
58146      * A numeric pixel value used to offset the default position of the anchor arrow.  When the anchor
58147      * position is on the top or bottom of the tooltip, `anchorOffset` will be used as a horizontal offset.
58148      * Likewise, when the anchor position is on the left or right side, `anchorOffset` will be used as
58149      * a vertical offset.
58150      */
58151     anchorOffset: 0,
58152     /**
58153      * @cfg {String} delegate
58154      *
58155      * A {@link Ext.DomQuery DomQuery} selector which allows selection of individual elements within the
58156      * `{@link #target}` element to trigger showing and hiding the ToolTip as the mouse moves within the
58157      * target.
58158      *
58159      * When specified, the child element of the target which caused a show event is placed into the
58160      * `{@link #triggerElement}` property before the ToolTip is shown.
58161      *
58162      * This may be useful when a Component has regular, repeating elements in it, each of which need a
58163      * ToolTip which contains information specific to that element.
58164      *
58165      * See the delegate example in class documentation of {@link Ext.tip.ToolTip}.
58166      */
58167
58168     // private
58169     targetCounter: 0,
58170     quickShowInterval: 250,
58171
58172     // private
58173     initComponent: function() {
58174         var me = this;
58175         me.callParent(arguments);
58176         me.lastActive = new Date();
58177         me.setTarget(me.target);
58178         me.origAnchor = me.anchor;
58179     },
58180
58181     // private
58182     onRender: function(ct, position) {
58183         var me = this;
58184         me.callParent(arguments);
58185         me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
58186         me.anchorEl = me.el.createChild({
58187             cls: Ext.baseCSSPrefix + 'tip-anchor ' + me.anchorCls
58188         });
58189     },
58190
58191     // private
58192     afterRender: function() {
58193         var me = this,
58194             zIndex;
58195
58196         me.callParent(arguments);
58197         zIndex = parseInt(me.el.getZIndex(), 10) || 0;
58198         me.anchorEl.setStyle('z-index', zIndex + 1).setVisibilityMode(Ext.Element.DISPLAY);
58199     },
58200
58201     /**
58202      * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
58203      * @param {String/HTMLElement/Ext.Element} t The Element, HtmlElement, or ID of an element to bind to
58204      */
58205     setTarget: function(target) {
58206         var me = this,
58207             t = Ext.get(target),
58208             tg;
58209
58210         if (me.target) {
58211             tg = Ext.get(me.target);
58212             me.mun(tg, 'mouseover', me.onTargetOver, me);
58213             me.mun(tg, 'mouseout', me.onTargetOut, me);
58214             me.mun(tg, 'mousemove', me.onMouseMove, me);
58215         }
58216
58217         me.target = t;
58218         if (t) {
58219
58220             me.mon(t, {
58221                 // TODO - investigate why IE6/7 seem to fire recursive resize in e.getXY
58222                 // breaking QuickTip#onTargetOver (EXTJSIV-1608)
58223                 freezeEvent: true,
58224
58225                 mouseover: me.onTargetOver,
58226                 mouseout: me.onTargetOut,
58227                 mousemove: me.onMouseMove,
58228                 scope: me
58229             });
58230         }
58231         if (me.anchor) {
58232             me.anchorTarget = me.target;
58233         }
58234     },
58235
58236     // private
58237     onMouseMove: function(e) {
58238         var me = this,
58239             t = me.delegate ? e.getTarget(me.delegate) : me.triggerElement = true,
58240             xy;
58241         if (t) {
58242             me.targetXY = e.getXY();
58243             if (t === me.triggerElement) {
58244                 if (!me.hidden && me.trackMouse) {
58245                     xy = me.getTargetXY();
58246                     if (me.constrainPosition) {
58247                         xy = me.el.adjustForConstraints(xy, me.el.getScopeParent());
58248                     }
58249                     me.setPagePosition(xy);
58250                 }
58251             } else {
58252                 me.hide();
58253                 me.lastActive = new Date(0);
58254                 me.onTargetOver(e);
58255             }
58256         } else if ((!me.closable && me.isVisible()) && me.autoHide !== false) {
58257             me.hide();
58258         }
58259     },
58260
58261     // private
58262     getTargetXY: function() {
58263         var me = this,
58264             mouseOffset;
58265         if (me.delegate) {
58266             me.anchorTarget = me.triggerElement;
58267         }
58268         if (me.anchor) {
58269             me.targetCounter++;
58270                 var offsets = me.getOffsets(),
58271                     xy = (me.anchorToTarget && !me.trackMouse) ? me.el.getAlignToXY(me.anchorTarget, me.getAnchorAlign()) : me.targetXY,
58272                     dw = Ext.Element.getViewWidth() - 5,
58273                     dh = Ext.Element.getViewHeight() - 5,
58274                     de = document.documentElement,
58275                     bd = document.body,
58276                     scrollX = (de.scrollLeft || bd.scrollLeft || 0) + 5,
58277                     scrollY = (de.scrollTop || bd.scrollTop || 0) + 5,
58278                     axy = [xy[0] + offsets[0], xy[1] + offsets[1]],
58279                     sz = me.getSize(),
58280                     constrainPosition = me.constrainPosition;
58281
58282             me.anchorEl.removeCls(me.anchorCls);
58283
58284             if (me.targetCounter < 2 && constrainPosition) {
58285                 if (axy[0] < scrollX) {
58286                     if (me.anchorToTarget) {
58287                         me.defaultAlign = 'l-r';
58288                         if (me.mouseOffset) {
58289                             me.mouseOffset[0] *= -1;
58290                         }
58291                     }
58292                     me.anchor = 'left';
58293                     return me.getTargetXY();
58294                 }
58295                 if (axy[0] + sz.width > dw) {
58296                     if (me.anchorToTarget) {
58297                         me.defaultAlign = 'r-l';
58298                         if (me.mouseOffset) {
58299                             me.mouseOffset[0] *= -1;
58300                         }
58301                     }
58302                     me.anchor = 'right';
58303                     return me.getTargetXY();
58304                 }
58305                 if (axy[1] < scrollY) {
58306                     if (me.anchorToTarget) {
58307                         me.defaultAlign = 't-b';
58308                         if (me.mouseOffset) {
58309                             me.mouseOffset[1] *= -1;
58310                         }
58311                     }
58312                     me.anchor = 'top';
58313                     return me.getTargetXY();
58314                 }
58315                 if (axy[1] + sz.height > dh) {
58316                     if (me.anchorToTarget) {
58317                         me.defaultAlign = 'b-t';
58318                         if (me.mouseOffset) {
58319                             me.mouseOffset[1] *= -1;
58320                         }
58321                     }
58322                     me.anchor = 'bottom';
58323                     return me.getTargetXY();
58324                 }
58325             }
58326
58327             me.anchorCls = Ext.baseCSSPrefix + 'tip-anchor-' + me.getAnchorPosition();
58328             me.anchorEl.addCls(me.anchorCls);
58329             me.targetCounter = 0;
58330             return axy;
58331         } else {
58332             mouseOffset = me.getMouseOffset();
58333             return (me.targetXY) ? [me.targetXY[0] + mouseOffset[0], me.targetXY[1] + mouseOffset[1]] : mouseOffset;
58334         }
58335     },
58336
58337     getMouseOffset: function() {
58338         var me = this,
58339         offset = me.anchor ? [0, 0] : [15, 18];
58340         if (me.mouseOffset) {
58341             offset[0] += me.mouseOffset[0];
58342             offset[1] += me.mouseOffset[1];
58343         }
58344         return offset;
58345     },
58346
58347     // private
58348     getAnchorPosition: function() {
58349         var me = this,
58350             m;
58351         if (me.anchor) {
58352             me.tipAnchor = me.anchor.charAt(0);
58353         } else {
58354             m = me.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);
58355             me.tipAnchor = m[1].charAt(0);
58356         }
58357
58358         switch (me.tipAnchor) {
58359         case 't':
58360             return 'top';
58361         case 'b':
58362             return 'bottom';
58363         case 'r':
58364             return 'right';
58365         }
58366         return 'left';
58367     },
58368
58369     // private
58370     getAnchorAlign: function() {
58371         switch (this.anchor) {
58372         case 'top':
58373             return 'tl-bl';
58374         case 'left':
58375             return 'tl-tr';
58376         case 'right':
58377             return 'tr-tl';
58378         default:
58379             return 'bl-tl';
58380         }
58381     },
58382
58383     // private
58384     getOffsets: function() {
58385         var me = this,
58386             mouseOffset,
58387             offsets,
58388             ap = me.getAnchorPosition().charAt(0);
58389         if (me.anchorToTarget && !me.trackMouse) {
58390             switch (ap) {
58391             case 't':
58392                 offsets = [0, 9];
58393                 break;
58394             case 'b':
58395                 offsets = [0, -13];
58396                 break;
58397             case 'r':
58398                 offsets = [ - 13, 0];
58399                 break;
58400             default:
58401                 offsets = [9, 0];
58402                 break;
58403             }
58404         } else {
58405             switch (ap) {
58406             case 't':
58407                 offsets = [ - 15 - me.anchorOffset, 30];
58408                 break;
58409             case 'b':
58410                 offsets = [ - 19 - me.anchorOffset, -13 - me.el.dom.offsetHeight];
58411                 break;
58412             case 'r':
58413                 offsets = [ - 15 - me.el.dom.offsetWidth, -13 - me.anchorOffset];
58414                 break;
58415             default:
58416                 offsets = [25, -13 - me.anchorOffset];
58417                 break;
58418             }
58419         }
58420         mouseOffset = me.getMouseOffset();
58421         offsets[0] += mouseOffset[0];
58422         offsets[1] += mouseOffset[1];
58423
58424         return offsets;
58425     },
58426
58427     // private
58428     onTargetOver: function(e) {
58429         var me = this,
58430             t;
58431
58432         if (me.disabled || e.within(me.target.dom, true)) {
58433             return;
58434         }
58435         t = e.getTarget(me.delegate);
58436         if (t) {
58437             me.triggerElement = t;
58438             me.clearTimer('hide');
58439             me.targetXY = e.getXY();
58440             me.delayShow();
58441         }
58442     },
58443
58444     // private
58445     delayShow: function() {
58446         var me = this;
58447         if (me.hidden && !me.showTimer) {
58448             if (Ext.Date.getElapsed(me.lastActive) < me.quickShowInterval) {
58449                 me.show();
58450             } else {
58451                 me.showTimer = Ext.defer(me.show, me.showDelay, me);
58452             }
58453         }
58454         else if (!me.hidden && me.autoHide !== false) {
58455             me.show();
58456         }
58457     },
58458
58459     // private
58460     onTargetOut: function(e) {
58461         var me = this;
58462         if (me.disabled || e.within(me.target.dom, true)) {
58463             return;
58464         }
58465         me.clearTimer('show');
58466         if (me.autoHide !== false) {
58467             me.delayHide();
58468         }
58469     },
58470
58471     // private
58472     delayHide: function() {
58473         var me = this;
58474         if (!me.hidden && !me.hideTimer) {
58475             me.hideTimer = Ext.defer(me.hide, me.hideDelay, me);
58476         }
58477     },
58478
58479     /**
58480      * Hides this tooltip if visible.
58481      */
58482     hide: function() {
58483         var me = this;
58484         me.clearTimer('dismiss');
58485         me.lastActive = new Date();
58486         if (me.anchorEl) {
58487             me.anchorEl.hide();
58488         }
58489         me.callParent(arguments);
58490         delete me.triggerElement;
58491     },
58492
58493     /**
58494      * Shows this tooltip at the current event target XY position.
58495      */
58496     show: function() {
58497         var me = this;
58498
58499         // Show this Component first, so that sizing can be calculated
58500         // pre-show it off screen so that the el will have dimensions
58501         this.callParent();
58502         if (this.hidden === false) {
58503             me.setPagePosition(-10000, -10000);
58504
58505             if (me.anchor) {
58506                 me.anchor = me.origAnchor;
58507             }
58508             me.showAt(me.getTargetXY());
58509
58510             if (me.anchor) {
58511                 me.syncAnchor();
58512                 me.anchorEl.show();
58513             } else {
58514                 me.anchorEl.hide();
58515             }
58516         }
58517     },
58518
58519     // inherit docs
58520     showAt: function(xy) {
58521         var me = this;
58522         me.lastActive = new Date();
58523         me.clearTimers();
58524
58525         // Only call if this is hidden. May have been called from show above.
58526         if (!me.isVisible()) {
58527             this.callParent(arguments);
58528         }
58529
58530         // Show may have been vetoed.
58531         if (me.isVisible()) {
58532             me.setPagePosition(xy[0], xy[1]);
58533             if (me.constrainPosition || me.constrain) {
58534                 me.doConstrain();
58535             }
58536             me.toFront(true);
58537         }
58538
58539         if (me.dismissDelay && me.autoHide !== false) {
58540             me.dismissTimer = Ext.defer(me.hide, me.dismissDelay, me);
58541         }
58542         if (me.anchor) {
58543             me.syncAnchor();
58544             if (!me.anchorEl.isVisible()) {
58545                 me.anchorEl.show();
58546             }
58547         } else {
58548             me.anchorEl.hide();
58549         }
58550     },
58551
58552     // private
58553     syncAnchor: function() {
58554         var me = this,
58555             anchorPos,
58556             targetPos,
58557             offset;
58558         switch (me.tipAnchor.charAt(0)) {
58559         case 't':
58560             anchorPos = 'b';
58561             targetPos = 'tl';
58562             offset = [20 + me.anchorOffset, 1];
58563             break;
58564         case 'r':
58565             anchorPos = 'l';
58566             targetPos = 'tr';
58567             offset = [ - 1, 12 + me.anchorOffset];
58568             break;
58569         case 'b':
58570             anchorPos = 't';
58571             targetPos = 'bl';
58572             offset = [20 + me.anchorOffset, -1];
58573             break;
58574         default:
58575             anchorPos = 'r';
58576             targetPos = 'tl';
58577             offset = [1, 12 + me.anchorOffset];
58578             break;
58579         }
58580         me.anchorEl.alignTo(me.el, anchorPos + '-' + targetPos, offset);
58581     },
58582
58583     // private
58584     setPagePosition: function(x, y) {
58585         var me = this;
58586         me.callParent(arguments);
58587         if (me.anchor) {
58588             me.syncAnchor();
58589         }
58590     },
58591
58592     // private
58593     clearTimer: function(name) {
58594         name = name + 'Timer';
58595         clearTimeout(this[name]);
58596         delete this[name];
58597     },
58598
58599     // private
58600     clearTimers: function() {
58601         var me = this;
58602         me.clearTimer('show');
58603         me.clearTimer('dismiss');
58604         me.clearTimer('hide');
58605     },
58606
58607     // private
58608     onShow: function() {
58609         var me = this;
58610         me.callParent();
58611         me.mon(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
58612     },
58613
58614     // private
58615     onHide: function() {
58616         var me = this;
58617         me.callParent();
58618         me.mun(Ext.getDoc(), 'mousedown', me.onDocMouseDown, me);
58619     },
58620
58621     // private
58622     onDocMouseDown: function(e) {
58623         var me = this;
58624         if (me.autoHide !== true && !me.closable && !e.within(me.el.dom)) {
58625             me.disable();
58626             Ext.defer(me.doEnable, 100, me);
58627         }
58628     },
58629
58630     // private
58631     doEnable: function() {
58632         if (!this.isDestroyed) {
58633             this.enable();
58634         }
58635     },
58636
58637     // private
58638     onDisable: function() {
58639         this.callParent();
58640         this.clearTimers();
58641         this.hide();
58642     },
58643
58644     beforeDestroy: function() {
58645         var me = this;
58646         me.clearTimers();
58647         Ext.destroy(me.anchorEl);
58648         delete me.anchorEl;
58649         delete me.target;
58650         delete me.anchorTarget;
58651         delete me.triggerElement;
58652         me.callParent();
58653     },
58654
58655     // private
58656     onDestroy: function() {
58657         Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
58658         this.callParent();
58659     }
58660 });
58661
58662 /**
58663  * @class Ext.tip.QuickTip
58664  * @extends Ext.tip.ToolTip
58665  * A specialized tooltip class for tooltips that can be specified in markup and automatically managed by the global
58666  * {@link Ext.tip.QuickTipManager} instance.  See the QuickTipManager documentation for additional usage details and examples.
58667  * @xtype quicktip
58668  */
58669 Ext.define('Ext.tip.QuickTip', {
58670     extend: 'Ext.tip.ToolTip',
58671     alternateClassName: 'Ext.QuickTip',
58672     /**
58673      * @cfg {String/HTMLElement/Ext.Element} target The target HTMLElement, Ext.Element or id to associate with this Quicktip (defaults to the document).
58674      */
58675     /**
58676      * @cfg {Boolean} interceptTitles True to automatically use the element's DOM title value if available.
58677      */
58678     interceptTitles : false,
58679
58680     // Force creation of header Component
58681     title: '&#160;',
58682
58683     // private
58684     tagConfig : {
58685         namespace : "data-",
58686         attribute : "qtip",
58687         width : "qwidth",
58688         target : "target",
58689         title : "qtitle",
58690         hide : "hide",
58691         cls : "qclass",
58692         align : "qalign",
58693         anchor : "anchor"
58694     },
58695
58696     // private
58697     initComponent : function(){
58698         var me = this;
58699
58700         me.target = me.target || Ext.getDoc();
58701         me.targets = me.targets || {};
58702         me.callParent();
58703     },
58704
58705     /**
58706      * Configures a new quick tip instance and assigns it to a target element.  The following config values are
58707      * supported (for example usage, see the {@link Ext.tip.QuickTipManager} class header):
58708      * <div class="mdetail-params"><ul>
58709      * <li>autoHide</li>
58710      * <li>cls</li>
58711      * <li>dismissDelay (overrides the singleton value)</li>
58712      * <li>target (required)</li>
58713      * <li>text (required)</li>
58714      * <li>title</li>
58715      * <li>width</li></ul></div>
58716      * @param {Object} config The config object
58717      */
58718     register : function(config){
58719         var configs = Ext.isArray(config) ? config : arguments,
58720             i = 0,
58721             len = configs.length,
58722             target, j, targetLen;
58723
58724         for (; i < len; i++) {
58725             config = configs[i];
58726             target = config.target;
58727             if (target) {
58728                 if (Ext.isArray(target)) {
58729                     for (j = 0, targetLen = target.length; j < targetLen; j++) {
58730                         this.targets[Ext.id(target[j])] = config;
58731                     }
58732                 } else{
58733                     this.targets[Ext.id(target)] = config;
58734                 }
58735             }
58736         }
58737     },
58738
58739     /**
58740      * Removes this quick tip from its element and destroys it.
58741      * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip is to be removed or ID of the element.
58742      */
58743     unregister : function(el){
58744         delete this.targets[Ext.id(el)];
58745     },
58746
58747     /**
58748      * Hides a visible tip or cancels an impending show for a particular element.
58749      * @param {String/HTMLElement/Ext.Element} el The element that is the target of the tip or ID of the element.
58750      */
58751     cancelShow: function(el){
58752         var me = this,
58753             activeTarget = me.activeTarget;
58754
58755         el = Ext.get(el).dom;
58756         if (me.isVisible()) {
58757             if (activeTarget && activeTarget.el == el) {
58758                 me.hide();
58759             }
58760         } else if (activeTarget && activeTarget.el == el) {
58761             me.clearTimer('show');
58762         }
58763     },
58764
58765     /**
58766      * @private
58767      * Reads the tip text from the closest node to the event target which contains the attribute we
58768      * are configured to look for. Returns an object containing the text from the attribute, and the target element from
58769      * which the text was read.
58770      */
58771     getTipCfg: function(e) {
58772         var t = e.getTarget(),
58773             titleText = t.title,
58774             cfg;
58775
58776         if (this.interceptTitles && titleText && Ext.isString(titleText)) {
58777             t.qtip = titleText;
58778             t.removeAttribute("title");
58779             e.preventDefault();
58780             return {
58781                 text: titleText
58782             };
58783         }
58784         else {
58785             cfg = this.tagConfig;
58786             t = e.getTarget('[' + cfg.namespace + cfg.attribute + ']');
58787             if (t) {
58788                 return {
58789                     target: t,
58790                     text: t.getAttribute(cfg.namespace + cfg.attribute)
58791                 };
58792             }
58793         }
58794     },
58795
58796     // private
58797     onTargetOver : function(e){
58798         var me = this,
58799             target = e.getTarget(),
58800             elTarget,
58801             cfg,
58802             ns,
58803             tipConfig,
58804             autoHide;
58805
58806         if (me.disabled) {
58807             return;
58808         }
58809
58810         // TODO - this causes "e" to be recycled in IE6/7 (EXTJSIV-1608) so ToolTip#setTarget
58811         // was changed to include freezeEvent. The issue seems to be a nested 'resize' event
58812         // that smashed Ext.EventObject.
58813         me.targetXY = e.getXY();
58814
58815         if(!target || target.nodeType !== 1 || target == document || target == document.body){
58816             return;
58817         }
58818
58819         if (me.activeTarget && ((target == me.activeTarget.el) || Ext.fly(me.activeTarget.el).contains(target))) {
58820             me.clearTimer('hide');
58821             me.show();
58822             return;
58823         }
58824
58825         if (target) {
58826             Ext.Object.each(me.targets, function(key, value) {
58827                 var targetEl = Ext.fly(value.target);
58828                 if (targetEl && (targetEl.dom === target || targetEl.contains(target))) {
58829                     elTarget = targetEl.dom;
58830                     return false;
58831                 }
58832             });
58833             if (elTarget) {
58834                 me.activeTarget = me.targets[elTarget.id];
58835                 me.activeTarget.el = target;
58836                 me.anchor = me.activeTarget.anchor;
58837                 if (me.anchor) {
58838                     me.anchorTarget = target;
58839                 }
58840                 me.delayShow();
58841                 return;
58842             }
58843         }
58844
58845         elTarget = Ext.get(target);
58846         cfg = me.tagConfig;
58847         ns = cfg.namespace;
58848         tipConfig = me.getTipCfg(e);
58849
58850         if (tipConfig) {
58851
58852             // getTipCfg may look up the parentNode axis for a tip text attribute and will return the new target node.
58853             // Change our target element to match that from which the tip text attribute was read.
58854             if (tipConfig.target) {
58855                 target = tipConfig.target;
58856                 elTarget = Ext.get(target);
58857             }
58858             autoHide = elTarget.getAttribute(ns + cfg.hide);
58859
58860             me.activeTarget = {
58861                 el: target,
58862                 text: tipConfig.text,
58863                 width: +elTarget.getAttribute(ns + cfg.width) || null,
58864                 autoHide: autoHide != "user" && autoHide !== 'false',
58865                 title: elTarget.getAttribute(ns + cfg.title),
58866                 cls: elTarget.getAttribute(ns + cfg.cls),
58867                 align: elTarget.getAttribute(ns + cfg.align)
58868
58869             };
58870             me.anchor = elTarget.getAttribute(ns + cfg.anchor);
58871             if (me.anchor) {
58872                 me.anchorTarget = target;
58873             }
58874             me.delayShow();
58875         }
58876     },
58877
58878     // private
58879     onTargetOut : function(e){
58880         var me = this;
58881
58882         // If moving within the current target, and it does not have a new tip, ignore the mouseout
58883         if (me.activeTarget && e.within(me.activeTarget.el) && !me.getTipCfg(e)) {
58884             return;
58885         }
58886
58887         me.clearTimer('show');
58888         if (me.autoHide !== false) {
58889             me.delayHide();
58890         }
58891     },
58892
58893     // inherit docs
58894     showAt : function(xy){
58895         var me = this,
58896             target = me.activeTarget;
58897
58898         if (target) {
58899             if (!me.rendered) {
58900                 me.render(Ext.getBody());
58901                 me.activeTarget = target;
58902             }
58903             if (target.title) {
58904                 me.setTitle(target.title || '');
58905                 me.header.show();
58906             } else {
58907                 me.header.hide();
58908             }
58909             me.body.update(target.text);
58910             me.autoHide = target.autoHide;
58911             me.dismissDelay = target.dismissDelay || me.dismissDelay;
58912             if (me.lastCls) {
58913                 me.el.removeCls(me.lastCls);
58914                 delete me.lastCls;
58915             }
58916             if (target.cls) {
58917                 me.el.addCls(target.cls);
58918                 me.lastCls = target.cls;
58919             }
58920
58921             me.setWidth(target.width);
58922
58923             if (me.anchor) {
58924                 me.constrainPosition = false;
58925             } else if (target.align) { // TODO: this doesn't seem to work consistently
58926                 xy = me.el.getAlignToXY(target.el, target.align);
58927                 me.constrainPosition = false;
58928             }else{
58929                 me.constrainPosition = true;
58930             }
58931         }
58932         me.callParent([xy]);
58933     },
58934
58935     // inherit docs
58936     hide: function(){
58937         delete this.activeTarget;
58938         this.callParent();
58939     }
58940 });
58941
58942 /**
58943  * @class Ext.tip.QuickTipManager
58944  *
58945  * Provides attractive and customizable tooltips for any element. The QuickTips
58946  * singleton is used to configure and manage tooltips globally for multiple elements
58947  * in a generic manner.  To create individual tooltips with maximum customizability,
58948  * you should consider either {@link Ext.tip.Tip} or {@link Ext.tip.ToolTip}.
58949  *
58950  * Quicktips can be configured via tag attributes directly in markup, or by
58951  * registering quick tips programmatically via the {@link #register} method.
58952  *
58953  * The singleton's instance of {@link Ext.tip.QuickTip} is available via
58954  * {@link #getQuickTip}, and supports all the methods, and all the all the
58955  * configuration properties of Ext.tip.QuickTip. These settings will apply to all
58956  * tooltips shown by the singleton.
58957  *
58958  * Below is the summary of the configuration properties which can be used.
58959  * For detailed descriptions see the config options for the {@link Ext.tip.QuickTip QuickTip} class
58960  *
58961  * ## QuickTips singleton configs (all are optional)
58962  *
58963  *  - `dismissDelay`
58964  *  - `hideDelay`
58965  *  - `maxWidth`
58966  *  - `minWidth`
58967  *  - `showDelay`
58968  *  - `trackMouse`
58969  *
58970  * ## Target element configs (optional unless otherwise noted)
58971  *
58972  *  - `autoHide`
58973  *  - `cls`
58974  *  - `dismissDelay` (overrides singleton value)
58975  *  - `target` (required)
58976  *  - `text` (required)
58977  *  - `title`
58978  *  - `width`
58979  *
58980  * Here is an example showing how some of these config options could be used:
58981  *
58982  *     @example
58983  *     // Init the singleton.  Any tag-based quick tips will start working.
58984  *     Ext.tip.QuickTipManager.init();
58985  *
58986  *     // Apply a set of config properties to the singleton
58987  *     Ext.apply(Ext.tip.QuickTipManager.getQuickTip(), {
58988  *         maxWidth: 200,
58989  *         minWidth: 100,
58990  *         showDelay: 50      // Show 50ms after entering target
58991  *     });
58992  *
58993  *     // Create a small panel to add a quick tip to
58994  *     Ext.create('Ext.container.Container', {
58995  *         id: 'quickTipContainer',
58996  *         width: 200,
58997  *         height: 150,
58998  *         style: {
58999  *             backgroundColor:'#000000'
59000  *         },
59001  *         renderTo: Ext.getBody()
59002  *     });
59003  *
59004  *
59005  *     // Manually register a quick tip for a specific element
59006  *     Ext.tip.QuickTipManager.register({
59007  *         target: 'quickTipContainer',
59008  *         title: 'My Tooltip',
59009  *         text: 'This tooltip was added in code',
59010  *         width: 100,
59011  *         dismissDelay: 10000 // Hide after 10 seconds hover
59012  *     });
59013  *
59014  * To register a quick tip in markup, you simply add one or more of the valid QuickTip attributes prefixed with
59015  * the **data-** namespace.  The HTML element itself is automatically set as the quick tip target. Here is the summary
59016  * of supported attributes (optional unless otherwise noted):
59017  *
59018  *  - `hide`: Specifying "user" is equivalent to setting autoHide = false.  Any other value will be the same as autoHide = true.
59019  *  - `qclass`: A CSS class to be applied to the quick tip (equivalent to the 'cls' target element config).
59020  *  - `qtip (required)`: The quick tip text (equivalent to the 'text' target element config).
59021  *  - `qtitle`: The quick tip title (equivalent to the 'title' target element config).
59022  *  - `qwidth`: The quick tip width (equivalent to the 'width' target element config).
59023  *
59024  * Here is an example of configuring an HTML element to display a tooltip from markup:
59025  *
59026  *     // Add a quick tip to an HTML button
59027  *     <input type="button" value="OK" data-qtitle="OK Button" data-qwidth="100"
59028  *          data-qtip="This is a quick tip from markup!"></input>
59029  *
59030  * @singleton
59031  */
59032 Ext.define('Ext.tip.QuickTipManager', function() {
59033     var tip,
59034         disabled = false;
59035
59036     return {
59037         requires: ['Ext.tip.QuickTip'],
59038         singleton: true,
59039         alternateClassName: 'Ext.QuickTips',
59040
59041         /**
59042          * Initialize the global QuickTips instance and prepare any quick tips.
59043          * @param {Boolean} autoRender (optional) True to render the QuickTips container immediately to
59044          * preload images. (Defaults to true)
59045          * @param {Object} config (optional) config object for the created QuickTip. By
59046          * default, the {@link Ext.tip.QuickTip QuickTip} class is instantiated, but this can
59047          * be changed by supplying an xtype property or a className property in this object.
59048          * All other properties on this object are configuration for the created component.
59049          */
59050         init : function (autoRender, config) {
59051             if (!tip) {
59052                 if (!Ext.isReady) {
59053                     Ext.onReady(function(){
59054                         Ext.tip.QuickTipManager.init(autoRender);
59055                     });
59056                     return;
59057                 }
59058
59059                 var tipConfig = Ext.apply({ disabled: disabled }, config),
59060                     className = tipConfig.className,
59061                     xtype = tipConfig.xtype;
59062
59063                 if (className) {
59064                     delete tipConfig.className;
59065                 } else if (xtype) {
59066                     className = 'widget.' + xtype;
59067                     delete tipConfig.xtype;
59068                 }
59069
59070                 if (autoRender !== false) {
59071                     tipConfig.renderTo = document.body;
59072
59073                 }
59074
59075                 tip = Ext.create(className || 'Ext.tip.QuickTip', tipConfig);
59076             }
59077         },
59078
59079         /**
59080          * Destroy the QuickTips instance.
59081          */
59082         destroy: function() {
59083             if (tip) {
59084                 var undef;
59085                 tip.destroy();
59086                 tip = undef;
59087             }
59088         },
59089
59090         // Protected method called by the dd classes
59091         ddDisable : function(){
59092             // don't disable it if we don't need to
59093             if(tip && !disabled){
59094                 tip.disable();
59095             }
59096         },
59097
59098         // Protected method called by the dd classes
59099         ddEnable : function(){
59100             // only enable it if it hasn't been disabled
59101             if(tip && !disabled){
59102                 tip.enable();
59103             }
59104         },
59105
59106         /**
59107          * Enable quick tips globally.
59108          */
59109         enable : function(){
59110             if(tip){
59111                 tip.enable();
59112             }
59113             disabled = false;
59114         },
59115
59116         /**
59117          * Disable quick tips globally.
59118          */
59119         disable : function(){
59120             if(tip){
59121                 tip.disable();
59122             }
59123             disabled = true;
59124         },
59125
59126         /**
59127          * Returns true if quick tips are enabled, else false.
59128          * @return {Boolean}
59129          */
59130         isEnabled : function(){
59131             return tip !== undefined && !tip.disabled;
59132         },
59133
59134         /**
59135          * Gets the single {@link Ext.tip.QuickTip QuickTip} instance used to show tips from all registered elements.
59136          * @return {Ext.tip.QuickTip}
59137          */
59138         getQuickTip : function(){
59139             return tip;
59140         },
59141
59142         /**
59143          * Configures a new quick tip instance and assigns it to a target element.  See
59144          * {@link Ext.tip.QuickTip#register} for details.
59145          * @param {Object} config The config object
59146          */
59147         register : function(){
59148             tip.register.apply(tip, arguments);
59149         },
59150
59151         /**
59152          * Removes any registered quick tip from the target element and destroys it.
59153          * @param {String/HTMLElement/Ext.Element} el The element from which the quick tip is to be removed or ID of the element.
59154          */
59155         unregister : function(){
59156             tip.unregister.apply(tip, arguments);
59157         },
59158
59159         /**
59160          * Alias of {@link #register}.
59161          * @param {Object} config The config object
59162          */
59163         tips : function(){
59164             tip.register.apply(tip, arguments);
59165         }
59166     };
59167 }());
59168 /**
59169  * Represents an Ext JS 4 application, which is typically a single page app using a {@link Ext.container.Viewport Viewport}.
59170  * A typical Ext.app.Application might look like this:
59171  *
59172  *     Ext.application({
59173  *         name: 'MyApp',
59174  *         launch: function() {
59175  *             Ext.create('Ext.container.Viewport', {
59176  *                 items: {
59177  *                     html: 'My App'
59178  *                 }
59179  *             });
59180  *         }
59181  *     });
59182  *
59183  * This does several things. First it creates a global variable called 'MyApp' - all of your Application's classes (such
59184  * as its Models, Views and Controllers) will reside under this single namespace, which drastically lowers the chances
59185  * of colliding global variables.
59186  *
59187  * When the page is ready and all of your JavaScript has loaded, your Application's {@link #launch} function is called,
59188  * at which time you can run the code that starts your app. Usually this consists of creating a Viewport, as we do in
59189  * the example above.
59190  *
59191  * # Telling Application about the rest of the app
59192  *
59193  * Because an Ext.app.Application represents an entire app, we should tell it about the other parts of the app - namely
59194  * the Models, Views and Controllers that are bundled with the application. Let's say we have a blog management app; we
59195  * might have Models and Controllers for Posts and Comments, and Views for listing, adding and editing Posts and Comments.
59196  * Here's how we'd tell our Application about all these things:
59197  *
59198  *     Ext.application({
59199  *         name: 'Blog',
59200  *         models: ['Post', 'Comment'],
59201  *         controllers: ['Posts', 'Comments'],
59202  *
59203  *         launch: function() {
59204  *             ...
59205  *         }
59206  *     });
59207  *
59208  * Note that we didn't actually list the Views directly in the Application itself. This is because Views are managed by
59209  * Controllers, so it makes sense to keep those dependencies there. The Application will load each of the specified
59210  * Controllers using the pathing conventions laid out in the [application architecture guide][mvc] -
59211  * in this case expecting the controllers to reside in `app/controller/Posts.js` and
59212  * `app/controller/Comments.js`. In turn, each Controller simply needs to list the Views it uses and they will be
59213  * automatically loaded. Here's how our Posts controller like be defined:
59214  *
59215  *     Ext.define('MyApp.controller.Posts', {
59216  *         extend: 'Ext.app.Controller',
59217  *         views: ['posts.List', 'posts.Edit'],
59218  *
59219  *         //the rest of the Controller here
59220  *     });
59221  *
59222  * Because we told our Application about our Models and Controllers, and our Controllers about their Views, Ext JS will
59223  * automatically load all of our app files for us. This means we don't have to manually add script tags into our html
59224  * files whenever we add a new class, but more importantly it enables us to create a minimized build of our entire
59225  * application using the Ext JS 4 SDK Tools.
59226  *
59227  * For more information about writing Ext JS 4 applications, please see the
59228  * [application architecture guide][mvc].
59229  *
59230  * [mvc]: #!/guide/application_architecture
59231  *
59232  * @docauthor Ed Spencer
59233  */
59234 Ext.define('Ext.app.Application', {
59235     extend: 'Ext.app.Controller',
59236
59237     requires: [
59238         'Ext.ModelManager',
59239         'Ext.data.Model',
59240         'Ext.data.StoreManager',
59241         'Ext.tip.QuickTipManager',
59242         'Ext.ComponentManager',
59243         'Ext.app.EventBus'
59244     ],
59245
59246     /**
59247      * @cfg {String} name The name of your application. This will also be the namespace for your views, controllers
59248      * models and stores. Don't use spaces or special characters in the name.
59249      */
59250
59251     /**
59252      * @cfg {Object} scope The scope to execute the {@link #launch} function in. Defaults to the Application
59253      * instance.
59254      */
59255     scope: undefined,
59256
59257     /**
59258      * @cfg {Boolean} enableQuickTips True to automatically set up Ext.tip.QuickTip support.
59259      */
59260     enableQuickTips: true,
59261
59262     /**
59263      * @cfg {String} defaultUrl When the app is first loaded, this url will be redirected to.
59264      */
59265
59266     /**
59267      * @cfg {String} appFolder The path to the directory which contains all application's classes.
59268      * This path will be registered via {@link Ext.Loader#setPath} for the namespace specified in the {@link #name name} config.
59269      */
59270     appFolder: 'app',
59271
59272     /**
59273      * @cfg {Boolean} autoCreateViewport True to automatically load and instantiate AppName.view.Viewport
59274      * before firing the launch function.
59275      */
59276     autoCreateViewport: false,
59277
59278     /**
59279      * Creates new Application.
59280      * @param {Object} [config] Config object.
59281      */
59282     constructor: function(config) {
59283         config = config || {};
59284         Ext.apply(this, config);
59285
59286         var requires = config.requires || [];
59287
59288         Ext.Loader.setPath(this.name, this.appFolder);
59289
59290         if (this.paths) {
59291             Ext.Object.each(this.paths, function(key, value) {
59292                 Ext.Loader.setPath(key, value);
59293             });
59294         }
59295
59296         this.callParent(arguments);
59297
59298         this.eventbus = Ext.create('Ext.app.EventBus');
59299
59300         var controllers = Ext.Array.from(this.controllers),
59301             ln = controllers && controllers.length,
59302             i, controller;
59303
59304         this.controllers = Ext.create('Ext.util.MixedCollection');
59305
59306         if (this.autoCreateViewport) {
59307             requires.push(this.getModuleClassName('Viewport', 'view'));
59308         }
59309
59310         for (i = 0; i < ln; i++) {
59311             requires.push(this.getModuleClassName(controllers[i], 'controller'));
59312         }
59313
59314         Ext.require(requires);
59315
59316         Ext.onReady(function() {
59317             for (i = 0; i < ln; i++) {
59318                 controller = this.getController(controllers[i]);
59319                 controller.init(this);
59320             }
59321
59322             this.onBeforeLaunch.call(this);
59323         }, this);
59324     },
59325
59326     control: function(selectors, listeners, controller) {
59327         this.eventbus.control(selectors, listeners, controller);
59328     },
59329
59330     /**
59331      * Called automatically when the page has completely loaded. This is an empty function that should be
59332      * overridden by each application that needs to take action on page load
59333      * @property launch
59334      * @type Function
59335      * @param {String} profile The detected {@link #profiles application profile}
59336      * @return {Boolean} By default, the Application will dispatch to the configured startup controller and
59337      * action immediately after running the launch function. Return false to prevent this behavior.
59338      */
59339     launch: Ext.emptyFn,
59340
59341     /**
59342      * @private
59343      */
59344     onBeforeLaunch: function() {
59345         if (this.enableQuickTips) {
59346             Ext.tip.QuickTipManager.init();
59347         }
59348
59349         if (this.autoCreateViewport) {
59350             this.getView('Viewport').create();
59351         }
59352
59353         this.launch.call(this.scope || this);
59354         this.launched = true;
59355         this.fireEvent('launch', this);
59356
59357         this.controllers.each(function(controller) {
59358             controller.onLaunch(this);
59359         }, this);
59360     },
59361
59362     getModuleClassName: function(name, type) {
59363         var namespace = Ext.Loader.getPrefix(name);
59364
59365         if (namespace.length > 0 && namespace !== name) {
59366             return name;
59367         }
59368
59369         return this.name + '.' + type + '.' + name;
59370     },
59371
59372     getController: function(name) {
59373         var controller = this.controllers.get(name);
59374
59375         if (!controller) {
59376             controller = Ext.create(this.getModuleClassName(name, 'controller'), {
59377                 application: this,
59378                 id: name
59379             });
59380
59381             this.controllers.add(controller);
59382         }
59383
59384         return controller;
59385     },
59386
59387     getStore: function(name) {
59388         var store = Ext.StoreManager.get(name);
59389
59390         if (!store) {
59391             store = Ext.create(this.getModuleClassName(name, 'store'), {
59392                 storeId: name
59393             });
59394         }
59395
59396         return store;
59397     },
59398
59399     getModel: function(model) {
59400         model = this.getModuleClassName(model, 'model');
59401
59402         return Ext.ModelManager.getModel(model);
59403     },
59404
59405     getView: function(view) {
59406         view = this.getModuleClassName(view, 'view');
59407
59408         return Ext.ClassManager.get(view);
59409     }
59410 });
59411
59412 /**
59413  * @class Ext.chart.Callout
59414  * A mixin providing callout functionality for Ext.chart.series.Series.
59415  */
59416 Ext.define('Ext.chart.Callout', {
59417
59418     /* Begin Definitions */
59419
59420     /* End Definitions */
59421
59422     constructor: function(config) {
59423         if (config.callouts) {
59424             config.callouts.styles = Ext.applyIf(config.callouts.styles || {}, {
59425                 color: "#000",
59426                 font: "11px Helvetica, sans-serif"
59427             });
59428             this.callouts = Ext.apply(this.callouts || {}, config.callouts);
59429             this.calloutsArray = [];
59430         }
59431     },
59432
59433     renderCallouts: function() {
59434         if (!this.callouts) {
59435             return;
59436         }
59437
59438         var me = this,
59439             items = me.items,
59440             animate = me.chart.animate,
59441             config = me.callouts,
59442             styles = config.styles,
59443             group = me.calloutsArray,
59444             store = me.chart.store,
59445             len = store.getCount(),
59446             ratio = items.length / len,
59447             previouslyPlacedCallouts = [],
59448             i,
59449             count,
59450             j,
59451             p;
59452             
59453         for (i = 0, count = 0; i < len; i++) {
59454             for (j = 0; j < ratio; j++) {
59455                 var item = items[count],
59456                     label = group[count],
59457                     storeItem = store.getAt(i),
59458                     display;
59459                 
59460                 display = config.filter(storeItem);
59461                 
59462                 if (!display && !label) {
59463                     count++;
59464                     continue;               
59465                 }
59466                 
59467                 if (!label) {
59468                     group[count] = label = me.onCreateCallout(storeItem, item, i, display, j, count);
59469                 }
59470                 for (p in label) {
59471                     if (label[p] && label[p].setAttributes) {
59472                         label[p].setAttributes(styles, true);
59473                     }
59474                 }
59475                 if (!display) {
59476                     for (p in label) {
59477                         if (label[p]) {
59478                             if (label[p].setAttributes) {
59479                                 label[p].setAttributes({
59480                                     hidden: true
59481                                 }, true);
59482                             } else if(label[p].setVisible) {
59483                                 label[p].setVisible(false);
59484                             }
59485                         }
59486                     }
59487                 }
59488                 config.renderer(label, storeItem);
59489                 me.onPlaceCallout(label, storeItem, item, i, display, animate,
59490                                   j, count, previouslyPlacedCallouts);
59491                 previouslyPlacedCallouts.push(label);
59492                 count++;
59493             }
59494         }
59495         this.hideCallouts(count);
59496     },
59497
59498     onCreateCallout: function(storeItem, item, i, display) {
59499         var me = this,
59500             group = me.calloutsGroup,
59501             config = me.callouts,
59502             styles = config.styles,
59503             width = styles.width,
59504             height = styles.height,
59505             chart = me.chart,
59506             surface = chart.surface,
59507             calloutObj = {
59508                 //label: false,
59509                 //box: false,
59510                 lines: false
59511             };
59512
59513         calloutObj.lines = surface.add(Ext.apply({},
59514         {
59515             type: 'path',
59516             path: 'M0,0',
59517             stroke: me.getLegendColor() || '#555'
59518         },
59519         styles));
59520
59521         if (config.items) {
59522             calloutObj.panel = Ext.create('widget.panel', {
59523                 style: "position: absolute;",    
59524                 width: width,
59525                 height: height,
59526                 items: config.items,
59527                 renderTo: chart.el
59528             });
59529         }
59530
59531         return calloutObj;
59532     },
59533
59534     hideCallouts: function(index) {
59535         var calloutsArray = this.calloutsArray,
59536             len = calloutsArray.length,
59537             co,
59538             p;
59539         while (len-->index) {
59540             co = calloutsArray[len];
59541             for (p in co) {
59542                 if (co[p]) {
59543                     co[p].hide(true);
59544                 }
59545             }
59546         }
59547     }
59548 });
59549
59550 /**
59551  * @class Ext.draw.CompositeSprite
59552  * @extends Ext.util.MixedCollection
59553  *
59554  * A composite Sprite handles a group of sprites with common methods to a sprite
59555  * such as `hide`, `show`, `setAttributes`. These methods are applied to the set of sprites
59556  * added to the group.
59557  *
59558  * CompositeSprite extends {@link Ext.util.MixedCollection} so you can use the same methods
59559  * in `MixedCollection` to iterate through sprites, add and remove elements, etc.
59560  *
59561  * In order to create a CompositeSprite, one has to provide a handle to the surface where it is
59562  * rendered:
59563  *
59564  *     var group = Ext.create('Ext.draw.CompositeSprite', {
59565  *         surface: drawComponent.surface
59566  *     });
59567  *                  
59568  * Then just by using `MixedCollection` methods it's possible to add {@link Ext.draw.Sprite}s:
59569  *  
59570  *     group.add(sprite1);
59571  *     group.add(sprite2);
59572  *     group.add(sprite3);
59573  *                  
59574  * And then apply common Sprite methods to them:
59575  *  
59576  *     group.setAttributes({
59577  *         fill: '#f00'
59578  *     }, true);
59579  */
59580 Ext.define('Ext.draw.CompositeSprite', {
59581
59582     /* Begin Definitions */
59583
59584     extend: 'Ext.util.MixedCollection',
59585     mixins: {
59586         animate: 'Ext.util.Animate'
59587     },
59588
59589     /* End Definitions */
59590     isCompositeSprite: true,
59591     constructor: function(config) {
59592         var me = this;
59593         
59594         config = config || {};
59595         Ext.apply(me, config);
59596
59597         me.addEvents(
59598             'mousedown',
59599             'mouseup',
59600             'mouseover',
59601             'mouseout',
59602             'click'
59603         );
59604         me.id = Ext.id(null, 'ext-sprite-group-');
59605         me.callParent();
59606     },
59607
59608     // @private
59609     onClick: function(e) {
59610         this.fireEvent('click', e);
59611     },
59612
59613     // @private
59614     onMouseUp: function(e) {
59615         this.fireEvent('mouseup', e);
59616     },
59617
59618     // @private
59619     onMouseDown: function(e) {
59620         this.fireEvent('mousedown', e);
59621     },
59622
59623     // @private
59624     onMouseOver: function(e) {
59625         this.fireEvent('mouseover', e);
59626     },
59627
59628     // @private
59629     onMouseOut: function(e) {
59630         this.fireEvent('mouseout', e);
59631     },
59632
59633     attachEvents: function(o) {
59634         var me = this;
59635         
59636         o.on({
59637             scope: me,
59638             mousedown: me.onMouseDown,
59639             mouseup: me.onMouseUp,
59640             mouseover: me.onMouseOver,
59641             mouseout: me.onMouseOut,
59642             click: me.onClick
59643         });
59644     },
59645
59646     // Inherit docs from MixedCollection
59647     add: function(key, o) {
59648         var result = this.callParent(arguments);
59649         this.attachEvents(result);
59650         return result;
59651     },
59652
59653     insert: function(index, key, o) {
59654         return this.callParent(arguments);
59655     },
59656
59657     // Inherit docs from MixedCollection
59658     remove: function(o) {
59659         var me = this;
59660         
59661         o.un({
59662             scope: me,
59663             mousedown: me.onMouseDown,
59664             mouseup: me.onMouseUp,
59665             mouseover: me.onMouseOver,
59666             mouseout: me.onMouseOut,
59667             click: me.onClick
59668         });
59669         return me.callParent(arguments);
59670     },
59671     
59672     /**
59673      * Returns the group bounding box.
59674      * Behaves like {@link Ext.draw.Sprite#getBBox} method.
59675      * @return {Object} an object with x, y, width, and height properties.
59676      */
59677     getBBox: function() {
59678         var i = 0,
59679             sprite,
59680             bb,
59681             items = this.items,
59682             len = this.length,
59683             infinity = Infinity,
59684             minX = infinity,
59685             maxHeight = -infinity,
59686             minY = infinity,
59687             maxWidth = -infinity,
59688             maxWidthBBox, maxHeightBBox;
59689         
59690         for (; i < len; i++) {
59691             sprite = items[i];
59692             if (sprite.el) {
59693                 bb = sprite.getBBox();
59694                 minX = Math.min(minX, bb.x);
59695                 minY = Math.min(minY, bb.y);
59696                 maxHeight = Math.max(maxHeight, bb.height + bb.y);
59697                 maxWidth = Math.max(maxWidth, bb.width + bb.x);
59698             }
59699         }
59700         
59701         return {
59702             x: minX,
59703             y: minY,
59704             height: maxHeight - minY,
59705             width: maxWidth - minX
59706         };
59707     },
59708
59709     /**
59710      * Iterates through all sprites calling `setAttributes` on each one. For more information {@link Ext.draw.Sprite}
59711      * provides a description of the attributes that can be set with this method.
59712      * @param {Object} attrs Attributes to be changed on the sprite.
59713      * @param {Boolean} redraw Flag to immediatly draw the change.
59714      * @return {Ext.draw.CompositeSprite} this
59715      */
59716     setAttributes: function(attrs, redraw) {
59717         var i = 0,
59718             items = this.items,
59719             len = this.length;
59720             
59721         for (; i < len; i++) {
59722             items[i].setAttributes(attrs, redraw);
59723         }
59724         return this;
59725     },
59726
59727     /**
59728      * Hides all sprites. If the first parameter of the method is true
59729      * then a redraw will be forced for each sprite.
59730      * @param {Boolean} redraw Flag to immediatly draw the change.
59731      * @return {Ext.draw.CompositeSprite} this
59732      */
59733     hide: function(redraw) {
59734         var i = 0,
59735             items = this.items,
59736             len = this.length;
59737             
59738         for (; i < len; i++) {
59739             items[i].hide(redraw);
59740         }
59741         return this;
59742     },
59743
59744     /**
59745      * Shows all sprites. If the first parameter of the method is true
59746      * then a redraw will be forced for each sprite.
59747      * @param {Boolean} redraw Flag to immediatly draw the change.
59748      * @return {Ext.draw.CompositeSprite} this
59749      */
59750     show: function(redraw) {
59751         var i = 0,
59752             items = this.items,
59753             len = this.length;
59754             
59755         for (; i < len; i++) {
59756             items[i].show(redraw);
59757         }
59758         return this;
59759     },
59760
59761     redraw: function() {
59762         var me = this,
59763             i = 0,
59764             items = me.items,
59765             surface = me.getSurface(),
59766             len = me.length;
59767         
59768         if (surface) {
59769             for (; i < len; i++) {
59770                 surface.renderItem(items[i]);
59771             }
59772         }
59773         return me;
59774     },
59775
59776     setStyle: function(obj) {
59777         var i = 0,
59778             items = this.items,
59779             len = this.length,
59780             item, el;
59781             
59782         for (; i < len; i++) {
59783             item = items[i];
59784             el = item.el;
59785             if (el) {
59786                 el.setStyle(obj);
59787             }
59788         }
59789     },
59790
59791     addCls: function(obj) {
59792         var i = 0,
59793             items = this.items,
59794             surface = this.getSurface(),
59795             len = this.length;
59796         
59797         if (surface) {
59798             for (; i < len; i++) {
59799                 surface.addCls(items[i], obj);
59800             }
59801         }
59802     },
59803
59804     removeCls: function(obj) {
59805         var i = 0,
59806             items = this.items,
59807             surface = this.getSurface(),
59808             len = this.length;
59809         
59810         if (surface) {
59811             for (; i < len; i++) {
59812                 surface.removeCls(items[i], obj);
59813             }
59814         }
59815     },
59816     
59817     /**
59818      * Grab the surface from the items
59819      * @private
59820      * @return {Ext.draw.Surface} The surface, null if not found
59821      */
59822     getSurface: function(){
59823         var first = this.first();
59824         if (first) {
59825             return first.surface;
59826         }
59827         return null;
59828     },
59829     
59830     /**
59831      * Destroys the SpriteGroup
59832      */
59833     destroy: function(){
59834         var me = this,
59835             surface = me.getSurface(),
59836             item;
59837             
59838         if (surface) {
59839             while (me.getCount() > 0) {
59840                 item = me.first();
59841                 me.remove(item);
59842                 surface.remove(item);
59843             }
59844         }
59845         me.clearListeners();
59846     }
59847 });
59848
59849 /**
59850  * @class Ext.layout.component.Auto
59851  * @extends Ext.layout.component.Component
59852  * @private
59853  *
59854  * <p>The AutoLayout is the default layout manager delegated by {@link Ext.Component} to
59855  * render any child Elements when no <tt>{@link Ext.container.Container#layout layout}</tt> is configured.</p>
59856  */
59857
59858 Ext.define('Ext.layout.component.Auto', {
59859
59860     /* Begin Definitions */
59861
59862     alias: 'layout.autocomponent',
59863
59864     extend: 'Ext.layout.component.Component',
59865
59866     /* End Definitions */
59867
59868     type: 'autocomponent',
59869
59870     onLayout : function(width, height) {
59871         this.setTargetSize(width, height);
59872     }
59873 });
59874 /**
59875  * @class Ext.chart.theme.Theme
59876  * 
59877  * Provides chart theming.
59878  * 
59879  * Used as mixins by Ext.chart.Chart.
59880  */
59881 Ext.define('Ext.chart.theme.Theme', {
59882
59883     /* Begin Definitions */
59884
59885     requires: ['Ext.draw.Color'],
59886
59887     /* End Definitions */
59888
59889     theme: 'Base',
59890     themeAttrs: false,
59891     
59892     initTheme: function(theme) {
59893         var me = this,
59894             themes = Ext.chart.theme,
59895             key, gradients;
59896         if (theme) {
59897             theme = theme.split(':');
59898             for (key in themes) {
59899                 if (key == theme[0]) {
59900                     gradients = theme[1] == 'gradients';
59901                     me.themeAttrs = new themes[key]({
59902                         useGradients: gradients
59903                     });
59904                     if (gradients) {
59905                         me.gradients = me.themeAttrs.gradients;
59906                     }
59907                     if (me.themeAttrs.background) {
59908                         me.background = me.themeAttrs.background;
59909                     }
59910                     return;
59911                 }
59912             }
59913         }
59914     }
59915 }, 
59916 // This callback is executed right after when the class is created. This scope refers to the newly created class itself
59917 function() {
59918    /* Theme constructor: takes either a complex object with styles like:
59919   
59920    {
59921         axis: {
59922             fill: '#000',
59923             'stroke-width': 1
59924         },
59925         axisLabelTop: {
59926             fill: '#000',
59927             font: '11px Arial'
59928         },
59929         axisLabelLeft: {
59930             fill: '#000',
59931             font: '11px Arial'
59932         },
59933         axisLabelRight: {
59934             fill: '#000',
59935             font: '11px Arial'
59936         },
59937         axisLabelBottom: {
59938             fill: '#000',
59939             font: '11px Arial'
59940         },
59941         axisTitleTop: {
59942             fill: '#000',
59943             font: '11px Arial'
59944         },
59945         axisTitleLeft: {
59946             fill: '#000',
59947             font: '11px Arial'
59948         },
59949         axisTitleRight: {
59950             fill: '#000',
59951             font: '11px Arial'
59952         },
59953         axisTitleBottom: {
59954             fill: '#000',
59955             font: '11px Arial'
59956         },
59957         series: {
59958             'stroke-width': 1
59959         },
59960         seriesLabel: {
59961             font: '12px Arial',
59962             fill: '#333'
59963         },
59964         marker: {
59965             stroke: '#555',
59966             fill: '#000',
59967             radius: 3,
59968             size: 3
59969         },
59970         seriesThemes: [{
59971             fill: '#C6DBEF'
59972         }, {
59973             fill: '#9ECAE1'
59974         }, {
59975             fill: '#6BAED6'
59976         }, {
59977             fill: '#4292C6'
59978         }, {
59979             fill: '#2171B5'
59980         }, {
59981             fill: '#084594'
59982         }],
59983         markerThemes: [{
59984             fill: '#084594',
59985             type: 'circle' 
59986         }, {
59987             fill: '#2171B5',
59988             type: 'cross'
59989         }, {
59990             fill: '#4292C6',
59991             type: 'plus'
59992         }]
59993     }
59994   
59995   ...or also takes just an array of colors and creates the complex object:
59996   
59997   {
59998       colors: ['#aaa', '#bcd', '#eee']
59999   }
60000   
60001   ...or takes just a base color and makes a theme from it
60002   
60003   {
60004       baseColor: '#bce'
60005   }
60006   
60007   To create a new theme you may add it to the Themes object:
60008   
60009   Ext.chart.theme.MyNewTheme = Ext.extend(Object, {
60010       constructor: function(config) {
60011           Ext.chart.theme.call(this, config, {
60012               baseColor: '#mybasecolor'
60013           });
60014       }
60015   });
60016   
60017   //Proposal:
60018   Ext.chart.theme.MyNewTheme = Ext.chart.createTheme('#basecolor');
60019   
60020   ...and then to use it provide the name of the theme (as a lower case string) in the chart config.
60021   
60022   {
60023       theme: 'mynewtheme'
60024   }
60025  */
60026
60027 (function() {
60028     Ext.chart.theme = function(config, base) {
60029         config = config || {};
60030         var i = 0, l, colors, color,
60031             seriesThemes, markerThemes,
60032             seriesTheme, markerTheme, 
60033             key, gradients = [],
60034             midColor, midL;
60035         
60036         if (config.baseColor) {
60037             midColor = Ext.draw.Color.fromString(config.baseColor);
60038             midL = midColor.getHSL()[2];
60039             if (midL < 0.15) {
60040                 midColor = midColor.getLighter(0.3);
60041             } else if (midL < 0.3) {
60042                 midColor = midColor.getLighter(0.15);
60043             } else if (midL > 0.85) {
60044                 midColor = midColor.getDarker(0.3);
60045             } else if (midL > 0.7) {
60046                 midColor = midColor.getDarker(0.15);
60047             }
60048             config.colors = [ midColor.getDarker(0.3).toString(),
60049                               midColor.getDarker(0.15).toString(),
60050                               midColor.toString(),
60051                               midColor.getLighter(0.15).toString(),
60052                               midColor.getLighter(0.3).toString()];
60053
60054             delete config.baseColor;
60055         }
60056         if (config.colors) {
60057             colors = config.colors.slice();
60058             markerThemes = base.markerThemes;
60059             seriesThemes = base.seriesThemes;
60060             l = colors.length;
60061             base.colors = colors;
60062             for (; i < l; i++) {
60063                 color = colors[i];
60064                 markerTheme = markerThemes[i] || {};
60065                 seriesTheme = seriesThemes[i] || {};
60066                 markerTheme.fill = seriesTheme.fill = markerTheme.stroke = seriesTheme.stroke = color;
60067                 markerThemes[i] = markerTheme;
60068                 seriesThemes[i] = seriesTheme;
60069             }
60070             base.markerThemes = markerThemes.slice(0, l);
60071             base.seriesThemes = seriesThemes.slice(0, l);
60072         //the user is configuring something in particular (either markers, series or pie slices)
60073         }
60074         for (key in base) {
60075             if (key in config) {
60076                 if (Ext.isObject(config[key]) && Ext.isObject(base[key])) {
60077                     Ext.apply(base[key], config[key]);
60078                 } else {
60079                     base[key] = config[key];
60080                 }
60081             }
60082         }
60083         if (config.useGradients) {
60084             colors = base.colors || (function () {
60085                 var ans = [];
60086                 for (i = 0, seriesThemes = base.seriesThemes, l = seriesThemes.length; i < l; i++) {
60087                     ans.push(seriesThemes[i].fill || seriesThemes[i].stroke);
60088                 }
60089                 return ans;
60090             })();
60091             for (i = 0, l = colors.length; i < l; i++) {
60092                 midColor = Ext.draw.Color.fromString(colors[i]);
60093                 if (midColor) {
60094                     color = midColor.getDarker(0.1).toString();
60095                     midColor = midColor.toString();
60096                     key = 'theme-' + midColor.substr(1) + '-' + color.substr(1);
60097                     gradients.push({
60098                         id: key,
60099                         angle: 45,
60100                         stops: {
60101                             0: {
60102                                 color: midColor.toString()
60103                             },
60104                             100: {
60105                                 color: color.toString()
60106                             }
60107                         }
60108                     });
60109                     colors[i] = 'url(#' + key + ')'; 
60110                 }
60111             }
60112             base.gradients = gradients;
60113             base.colors = colors;
60114         }
60115         /*
60116         base.axis = Ext.apply(base.axis || {}, config.axis || {});
60117         base.axisLabel = Ext.apply(base.axisLabel || {}, config.axisLabel || {});
60118         base.axisTitle = Ext.apply(base.axisTitle || {}, config.axisTitle || {});
60119         */
60120         Ext.apply(this, base);
60121     };
60122 })();
60123 });
60124
60125 /**
60126  * @class Ext.chart.Mask
60127  *
60128  * Defines a mask for a chart's series.
60129  * The 'chart' member must be set prior to rendering.
60130  *
60131  * A Mask can be used to select a certain region in a chart.
60132  * When enabled, the `select` event will be triggered when a
60133  * region is selected by the mask, allowing the user to perform
60134  * other tasks like zooming on that region, etc.
60135  *
60136  * In order to use the mask one has to set the Chart `mask` option to
60137  * `true`, `vertical` or `horizontal`. Then a possible configuration for the
60138  * listener could be:
60139  *
60140         items: {
60141             xtype: 'chart',
60142             animate: true,
60143             store: store1,
60144             mask: 'horizontal',
60145             listeners: {
60146                 select: {
60147                     fn: function(me, selection) {
60148                         me.setZoom(selection);
60149                         me.mask.hide();
60150                     }
60151                 }
60152             },
60153
60154  * In this example we zoom the chart to that particular region. You can also get
60155  * a handle to a mask instance from the chart object. The `chart.mask` element is a
60156  * `Ext.Panel`.
60157  * 
60158  */
60159 Ext.define('Ext.chart.Mask', {
60160     require: ['Ext.chart.MaskLayer'],
60161     /**
60162      * Creates new Mask.
60163      * @param {Object} config (optional) Config object.
60164      */
60165     constructor: function(config) {
60166         var me = this;
60167
60168         me.addEvents('select');
60169
60170         if (config) {
60171             Ext.apply(me, config);
60172         }
60173         if (me.mask) {
60174             me.on('afterrender', function() {
60175                 //create a mask layer component
60176                 var comp = Ext.create('Ext.chart.MaskLayer', {
60177                     renderTo: me.el
60178                 });
60179                 comp.el.on({
60180                     'mousemove': function(e) {
60181                         me.onMouseMove(e);
60182                     },
60183                     'mouseup': function(e) {
60184                         me.resized(e);
60185                     }
60186                 });
60187                 //create a resize handler for the component
60188                 var resizeHandler = Ext.create('Ext.resizer.Resizer', {
60189                     el: comp.el,
60190                     handles: 'all',
60191                     pinned: true
60192                 });
60193                 resizeHandler.on({
60194                     'resize': function(e) {
60195                         me.resized(e);    
60196                     }    
60197                 });
60198                 comp.initDraggable();
60199                 me.maskType = me.mask;
60200                 me.mask = comp;
60201                 me.maskSprite = me.surface.add({
60202                     type: 'path',
60203                     path: ['M', 0, 0],
60204                     zIndex: 1001,
60205                     opacity: 0.7,
60206                     hidden: true,
60207                     stroke: '#444'
60208                 });
60209             }, me, { single: true });
60210         }
60211     },
60212     
60213     resized: function(e) {
60214         var me = this,
60215             bbox = me.bbox || me.chartBBox,
60216             x = bbox.x,
60217             y = bbox.y,
60218             width = bbox.width,
60219             height = bbox.height,
60220             box = me.mask.getBox(true),
60221             max = Math.max,
60222             min = Math.min,
60223             staticX = box.x - x,
60224             staticY = box.y - y;
60225         
60226         staticX = max(staticX, x);
60227         staticY = max(staticY, y);
60228         staticX = min(staticX, width);
60229         staticY = min(staticY, height);
60230         box.x = staticX;
60231         box.y = staticY;
60232         me.fireEvent('select', me, box);
60233     },
60234
60235     onMouseUp: function(e) {
60236         var me = this,
60237             bbox = me.bbox || me.chartBBox,
60238             sel = me.maskSelection;
60239         me.maskMouseDown = false;
60240         me.mouseDown = false;
60241         if (me.mouseMoved) {
60242             me.onMouseMove(e);
60243             me.mouseMoved = false;
60244             me.fireEvent('select', me, {
60245                 x: sel.x - bbox.x,
60246                 y: sel.y - bbox.y,
60247                 width: sel.width,
60248                 height: sel.height
60249             });
60250         }
60251     },
60252
60253     onMouseDown: function(e) {
60254         var me = this;
60255         me.mouseDown = true;
60256         me.mouseMoved = false;
60257         me.maskMouseDown = {
60258             x: e.getPageX() - me.el.getX(),
60259             y: e.getPageY() - me.el.getY()
60260         };
60261     },
60262
60263     onMouseMove: function(e) {
60264         var me = this,
60265             mask = me.maskType,
60266             bbox = me.bbox || me.chartBBox,
60267             x = bbox.x,
60268             y = bbox.y,
60269             math = Math,
60270             floor = math.floor,
60271             abs = math.abs,
60272             min = math.min,
60273             max = math.max,
60274             height = floor(y + bbox.height),
60275             width = floor(x + bbox.width),
60276             posX = e.getPageX(),
60277             posY = e.getPageY(),
60278             staticX = posX - me.el.getX(),
60279             staticY = posY - me.el.getY(),
60280             maskMouseDown = me.maskMouseDown,
60281             path;
60282         
60283         me.mouseMoved = me.mouseDown;
60284         staticX = max(staticX, x);
60285         staticY = max(staticY, y);
60286         staticX = min(staticX, width);
60287         staticY = min(staticY, height);
60288         if (maskMouseDown && me.mouseDown) {
60289             if (mask == 'horizontal') {
60290                 staticY = y;
60291                 maskMouseDown.y = height;
60292                 posY = me.el.getY() + bbox.height + me.insetPadding;
60293             }
60294             else if (mask == 'vertical') {
60295                 staticX = x;
60296                 maskMouseDown.x = width;
60297             }
60298             width = maskMouseDown.x - staticX;
60299             height = maskMouseDown.y - staticY;
60300             path = ['M', staticX, staticY, 'l', width, 0, 0, height, -width, 0, 'z'];
60301             me.maskSelection = {
60302                 x: width > 0 ? staticX : staticX + width,
60303                 y: height > 0 ? staticY : staticY + height,
60304                 width: abs(width),
60305                 height: abs(height)
60306             };
60307             me.mask.updateBox(me.maskSelection);
60308             me.mask.show();
60309             me.maskSprite.setAttributes({
60310                 hidden: true    
60311             }, true);
60312         }
60313         else {
60314             if (mask == 'horizontal') {
60315                 path = ['M', staticX, y, 'L', staticX, height];
60316             }
60317             else if (mask == 'vertical') {
60318                 path = ['M', x, staticY, 'L', width, staticY];
60319             }
60320             else {
60321                 path = ['M', staticX, y, 'L', staticX, height, 'M', x, staticY, 'L', width, staticY];
60322             }
60323             me.maskSprite.setAttributes({
60324                 path: path,
60325                 fill: me.maskMouseDown ? me.maskSprite.stroke : false,
60326                 'stroke-width': mask === true ? 1 : 3,
60327                 hidden: false
60328             }, true);
60329         }
60330     },
60331
60332     onMouseLeave: function(e) {
60333         var me = this;
60334         me.mouseMoved = false;
60335         me.mouseDown = false;
60336         me.maskMouseDown = false;
60337         me.mask.hide();
60338         me.maskSprite.hide(true);
60339     }
60340 });
60341     
60342 /**
60343  * @class Ext.chart.Navigation
60344  *
60345  * Handles panning and zooming capabilities.
60346  *
60347  * Used as mixin by Ext.chart.Chart.
60348  */
60349 Ext.define('Ext.chart.Navigation', {
60350
60351     constructor: function() {
60352         this.originalStore = this.store;
60353     },
60354
60355     /**
60356      * Zooms the chart to the specified selection range.
60357      * Can be used with a selection mask. For example:
60358      *
60359      *     items: {
60360      *         xtype: 'chart',
60361      *         animate: true,
60362      *         store: store1,
60363      *         mask: 'horizontal',
60364      *         listeners: {
60365      *             select: {
60366      *                 fn: function(me, selection) {
60367      *                     me.setZoom(selection);
60368      *                     me.mask.hide();
60369      *                 }
60370      *             }
60371      *         }
60372      *     }
60373      */
60374     setZoom: function(zoomConfig) {
60375         var me = this,
60376             axes = me.axes,
60377             bbox = me.chartBBox,
60378             xScale = 1 / bbox.width,
60379             yScale = 1 / bbox.height,
60380             zoomer = {
60381                 x : zoomConfig.x * xScale,
60382                 y : zoomConfig.y * yScale,
60383                 width : zoomConfig.width * xScale,
60384                 height : zoomConfig.height * yScale
60385             };
60386         axes.each(function(axis) {
60387             var ends = axis.calcEnds();
60388             if (axis.position == 'bottom' || axis.position == 'top') {
60389                 var from = (ends.to - ends.from) * zoomer.x + ends.from,
60390                     to = (ends.to - ends.from) * zoomer.width + from;
60391                 axis.minimum = from;
60392                 axis.maximum = to;
60393             } else {
60394                 var to = (ends.to - ends.from) * (1 - zoomer.y) + ends.from,
60395                     from = to - (ends.to - ends.from) * zoomer.height;
60396                 axis.minimum = from;
60397                 axis.maximum = to;
60398             }
60399         });
60400         me.redraw(false);
60401     },
60402
60403     /**
60404      * Restores the zoom to the original value. This can be used to reset
60405      * the previous zoom state set by `setZoom`. For example:
60406      *
60407      *     myChart.restoreZoom();
60408      */
60409     restoreZoom: function() {
60410         this.store = this.substore = this.originalStore;
60411         this.redraw(true);
60412     }
60413
60414 });
60415
60416 /**
60417  * @class Ext.chart.Shape
60418  * @ignore
60419  */
60420 Ext.define('Ext.chart.Shape', {
60421
60422     /* Begin Definitions */
60423
60424     singleton: true,
60425
60426     /* End Definitions */
60427
60428     circle: function (surface, opts) {
60429         return surface.add(Ext.apply({
60430             type: 'circle',
60431             x: opts.x,
60432             y: opts.y,
60433             stroke: null,
60434             radius: opts.radius
60435         }, opts));
60436     },
60437     line: function (surface, opts) {
60438         return surface.add(Ext.apply({
60439             type: 'rect',
60440             x: opts.x - opts.radius,
60441             y: opts.y - opts.radius,
60442             height: 2 * opts.radius,
60443             width: 2 * opts.radius / 5
60444         }, opts));
60445     },
60446     square: function (surface, opts) {
60447         return surface.add(Ext.applyIf({
60448             type: 'rect',
60449             x: opts.x - opts.radius,
60450             y: opts.y - opts.radius,
60451             height: 2 * opts.radius,
60452             width: 2 * opts.radius,
60453             radius: null
60454         }, opts));
60455     },
60456     triangle: function (surface, opts) {
60457         opts.radius *= 1.75;
60458         return surface.add(Ext.apply({
60459             type: 'path',
60460             stroke: null,
60461             path: "M".concat(opts.x, ",", opts.y, "m0-", opts.radius * 0.58, "l", opts.radius * 0.5, ",", opts.radius * 0.87, "-", opts.radius, ",0z")
60462         }, opts));
60463     },
60464     diamond: function (surface, opts) {
60465         var r = opts.radius;
60466         r *= 1.5;
60467         return surface.add(Ext.apply({
60468             type: 'path',
60469             stroke: null,
60470             path: ["M", opts.x, opts.y - r, "l", r, r, -r, r, -r, -r, r, -r, "z"]
60471         }, opts));
60472     },
60473     cross: function (surface, opts) {
60474         var r = opts.radius;
60475         r = r / 1.7;
60476         return surface.add(Ext.apply({
60477             type: 'path',
60478             stroke: null,
60479             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"])
60480         }, opts));
60481     },
60482     plus: function (surface, opts) {
60483         var r = opts.radius / 1.3;
60484         return surface.add(Ext.apply({
60485             type: 'path',
60486             stroke: null,
60487             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"])
60488         }, opts));
60489     },
60490     arrow: function (surface, opts) {
60491         var r = opts.radius;
60492         return surface.add(Ext.apply({
60493             type: 'path',
60494             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")
60495         }, opts));
60496     },
60497     drop: function (surface, x, y, text, size, angle) {
60498         size = size || 30;
60499         angle = angle || 0;
60500         surface.add({
60501             type: 'path',
60502             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'],
60503             fill: '#000',
60504             stroke: 'none',
60505             rotate: {
60506                 degrees: 22.5 - angle,
60507                 x: x,
60508                 y: y
60509             }
60510         });
60511         angle = (angle + 90) * Math.PI / 180;
60512         surface.add({
60513             type: 'text',
60514             x: x + size * Math.sin(angle) - 10, // Shift here, Not sure why.
60515             y: y + size * Math.cos(angle) + 5,
60516             text:  text,
60517             'font-size': size * 12 / 40,
60518             stroke: 'none',
60519             fill: '#fff'
60520         });
60521     }
60522 });
60523 /**
60524  * A Surface is an interface to render methods inside a draw {@link Ext.draw.Component}.
60525  * A Surface contains methods to render sprites, get bounding boxes of sprites, add
60526  * sprites to the canvas, initialize other graphic components, etc. One of the most used
60527  * methods for this class is the `add` method, to add Sprites to the surface.
60528  *
60529  * Most of the Surface methods are abstract and they have a concrete implementation
60530  * in VML or SVG engines.
60531  *
60532  * A Surface instance can be accessed as a property of a draw component. For example:
60533  *
60534  *     drawComponent.surface.add({
60535  *         type: 'circle',
60536  *         fill: '#ffc',
60537  *         radius: 100,
60538  *         x: 100,
60539  *         y: 100
60540  *     });
60541  *
60542  * The configuration object passed in the `add` method is the same as described in the {@link Ext.draw.Sprite}
60543  * class documentation.
60544  *
60545  * # Listeners
60546  *
60547  * You can also add event listeners to the surface using the `Observable` listener syntax. Supported events are:
60548  *
60549  * - mousedown
60550  * - mouseup
60551  * - mouseover
60552  * - mouseout
60553  * - mousemove
60554  * - mouseenter
60555  * - mouseleave
60556  * - click
60557  *
60558  * For example:
60559  *
60560  *     drawComponent.surface.on({
60561  *        'mousemove': function() {
60562  *             console.log('moving the mouse over the surface');
60563  *         }
60564  *     });
60565  *
60566  * # Example
60567  *
60568  *     var drawComponent = Ext.create('Ext.draw.Component', {
60569  *         width: 800,
60570  *         height: 600,
60571  *         renderTo: document.body
60572  *     }), surface = drawComponent.surface;
60573  *
60574  *     surface.add([{
60575  *         type: 'circle',
60576  *         radius: 10,
60577  *         fill: '#f00',
60578  *         x: 10,
60579  *         y: 10,
60580  *         group: 'circles'
60581  *     }, {
60582  *         type: 'circle',
60583  *         radius: 10,
60584  *         fill: '#0f0',
60585  *         x: 50,
60586  *         y: 50,
60587  *         group: 'circles'
60588  *     }, {
60589  *         type: 'circle',
60590  *         radius: 10,
60591  *         fill: '#00f',
60592  *         x: 100,
60593  *         y: 100,
60594  *         group: 'circles'
60595  *     }, {
60596  *         type: 'rect',
60597  *         width: 20,
60598  *         height: 20,
60599  *         fill: '#f00',
60600  *         x: 10,
60601  *         y: 10,
60602  *         group: 'rectangles'
60603  *     }, {
60604  *         type: 'rect',
60605  *         width: 20,
60606  *         height: 20,
60607  *         fill: '#0f0',
60608  *         x: 50,
60609  *         y: 50,
60610  *         group: 'rectangles'
60611  *     }, {
60612  *         type: 'rect',
60613  *         width: 20,
60614  *         height: 20,
60615  *         fill: '#00f',
60616  *         x: 100,
60617  *         y: 100,
60618  *         group: 'rectangles'
60619  *     }]);
60620  *
60621  *     // Get references to my groups
60622  *     circles = surface.getGroup('circles');
60623  *     rectangles = surface.getGroup('rectangles');
60624  *
60625  *     // Animate the circles down
60626  *     circles.animate({
60627  *         duration: 1000,
60628  *         to: {
60629  *             translate: {
60630  *                 y: 200
60631  *             }
60632  *         }
60633  *     });
60634  *
60635  *     // Animate the rectangles across
60636  *     rectangles.animate({
60637  *         duration: 1000,
60638  *         to: {
60639  *             translate: {
60640  *                 x: 200
60641  *             }
60642  *         }
60643  *     });
60644  */
60645 Ext.define('Ext.draw.Surface', {
60646
60647     /* Begin Definitions */
60648
60649     mixins: {
60650         observable: 'Ext.util.Observable'
60651     },
60652
60653     requires: ['Ext.draw.CompositeSprite'],
60654     uses: ['Ext.draw.engine.Svg', 'Ext.draw.engine.Vml'],
60655
60656     separatorRe: /[, ]+/,
60657
60658     statics: {
60659         /**
60660          * Creates and returns a new concrete Surface instance appropriate for the current environment.
60661          * @param {Object} config Initial configuration for the Surface instance
60662          * @param {String[]} enginePriority (Optional) order of implementations to use; the first one that is
60663          * available in the current environment will be used. Defaults to `['Svg', 'Vml']`.
60664          * @return {Object} The created Surface or false.
60665          * @static
60666          */
60667         create: function(config, enginePriority) {
60668             enginePriority = enginePriority || ['Svg', 'Vml'];
60669
60670             var i = 0,
60671                 len = enginePriority.length,
60672                 surfaceClass;
60673
60674             for (; i < len; i++) {
60675                 if (Ext.supports[enginePriority[i]]) {
60676                     return Ext.create('Ext.draw.engine.' + enginePriority[i], config);
60677                 }
60678             }
60679             return false;
60680         }
60681     },
60682
60683     /* End Definitions */
60684
60685     // @private
60686     availableAttrs: {
60687         blur: 0,
60688         "clip-rect": "0 0 1e9 1e9",
60689         cursor: "default",
60690         cx: 0,
60691         cy: 0,
60692         'dominant-baseline': 'auto',
60693         fill: "none",
60694         "fill-opacity": 1,
60695         font: '10px "Arial"',
60696         "font-family": '"Arial"',
60697         "font-size": "10",
60698         "font-style": "normal",
60699         "font-weight": 400,
60700         gradient: "",
60701         height: 0,
60702         hidden: false,
60703         href: "http://sencha.com/",
60704         opacity: 1,
60705         path: "M0,0",
60706         radius: 0,
60707         rx: 0,
60708         ry: 0,
60709         scale: "1 1",
60710         src: "",
60711         stroke: "#000",
60712         "stroke-dasharray": "",
60713         "stroke-linecap": "butt",
60714         "stroke-linejoin": "butt",
60715         "stroke-miterlimit": 0,
60716         "stroke-opacity": 1,
60717         "stroke-width": 1,
60718         target: "_blank",
60719         text: "",
60720         "text-anchor": "middle",
60721         title: "Ext Draw",
60722         width: 0,
60723         x: 0,
60724         y: 0,
60725         zIndex: 0
60726     },
60727
60728     /**
60729      * @cfg {Number} height
60730      * The height of this component in pixels (defaults to auto).
60731      */
60732     /**
60733      * @cfg {Number} width
60734      * The width of this component in pixels (defaults to auto).
60735      */
60736
60737     container: undefined,
60738     height: 352,
60739     width: 512,
60740     x: 0,
60741     y: 0,
60742
60743     /**
60744      * @private Flag indicating that the surface implementation requires sprites to be maintained
60745      * in order of their zIndex. Impls that don't require this can set it to false.
60746      */
60747     orderSpritesByZIndex: true,
60748
60749
60750     /**
60751      * Creates new Surface.
60752      * @param {Object} config (optional) Config object.
60753      */
60754     constructor: function(config) {
60755         var me = this;
60756         config = config || {};
60757         Ext.apply(me, config);
60758
60759         me.domRef = Ext.getDoc().dom;
60760
60761         me.customAttributes = {};
60762
60763         me.addEvents(
60764             'mousedown',
60765             'mouseup',
60766             'mouseover',
60767             'mouseout',
60768             'mousemove',
60769             'mouseenter',
60770             'mouseleave',
60771             'click'
60772         );
60773
60774         me.mixins.observable.constructor.call(me);
60775
60776         me.getId();
60777         me.initGradients();
60778         me.initItems();
60779         if (me.renderTo) {
60780             me.render(me.renderTo);
60781             delete me.renderTo;
60782         }
60783         me.initBackground(config.background);
60784     },
60785
60786     // @private called to initialize components in the surface
60787     // this is dependent on the underlying implementation.
60788     initSurface: Ext.emptyFn,
60789
60790     // @private called to setup the surface to render an item
60791     //this is dependent on the underlying implementation.
60792     renderItem: Ext.emptyFn,
60793
60794     // @private
60795     renderItems: Ext.emptyFn,
60796
60797     // @private
60798     setViewBox: function (x, y, width, height) {
60799         if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
60800             this.viewBox = {x: x, y: y, width: width, height: height};
60801             this.applyViewBox();
60802         }
60803     },
60804
60805     /**
60806      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
60807      *
60808      * For example:
60809      *
60810      *     drawComponent.surface.addCls(sprite, 'x-visible');
60811      *
60812      * @param {Object} sprite The sprite to add the class to.
60813      * @param {String/String[]} className The CSS class to add, or an array of classes
60814      * @method
60815      */
60816     addCls: Ext.emptyFn,
60817
60818     /**
60819      * Removes one or more CSS classes from the element.
60820      *
60821      * For example:
60822      *
60823      *     drawComponent.surface.removeCls(sprite, 'x-visible');
60824      *
60825      * @param {Object} sprite The sprite to remove the class from.
60826      * @param {String/String[]} className The CSS class to remove, or an array of classes
60827      * @method
60828      */
60829     removeCls: Ext.emptyFn,
60830
60831     /**
60832      * Sets CSS style attributes to an element.
60833      *
60834      * For example:
60835      *
60836      *     drawComponent.surface.setStyle(sprite, {
60837      *         'cursor': 'pointer'
60838      *     });
60839      *
60840      * @param {Object} sprite The sprite to add, or an array of classes to
60841      * @param {Object} styles An Object with CSS styles.
60842      * @method
60843      */
60844     setStyle: Ext.emptyFn,
60845
60846     // @private
60847     initGradients: function() {
60848         var gradients = this.gradients;
60849         if (gradients) {
60850             Ext.each(gradients, this.addGradient, this);
60851         }
60852     },
60853
60854     // @private
60855     initItems: function() {
60856         var items = this.items;
60857         this.items = Ext.create('Ext.draw.CompositeSprite');
60858         this.groups = Ext.create('Ext.draw.CompositeSprite');
60859         if (items) {
60860             this.add(items);
60861         }
60862     },
60863
60864     // @private
60865     initBackground: function(config) {
60866         var me = this,
60867             width = me.width,
60868             height = me.height,
60869             gradientId, gradient, backgroundSprite;
60870         if (config) {
60871             if (config.gradient) {
60872                 gradient = config.gradient;
60873                 gradientId = gradient.id;
60874                 me.addGradient(gradient);
60875                 me.background = me.add({
60876                     type: 'rect',
60877                     x: 0,
60878                     y: 0,
60879                     width: width,
60880                     height: height,
60881                     fill: 'url(#' + gradientId + ')'
60882                 });
60883             } else if (config.fill) {
60884                 me.background = me.add({
60885                     type: 'rect',
60886                     x: 0,
60887                     y: 0,
60888                     width: width,
60889                     height: height,
60890                     fill: config.fill
60891                 });
60892             } else if (config.image) {
60893                 me.background = me.add({
60894                     type: 'image',
60895                     x: 0,
60896                     y: 0,
60897                     width: width,
60898                     height: height,
60899                     src: config.image
60900                 });
60901             }
60902         }
60903     },
60904
60905     /**
60906      * Sets the size of the surface. Accomodates the background (if any) to fit the new size too.
60907      *
60908      * For example:
60909      *
60910      *     drawComponent.surface.setSize(500, 500);
60911      *
60912      * This method is generally called when also setting the size of the draw Component.
60913      *
60914      * @param {Number} w The new width of the canvas.
60915      * @param {Number} h The new height of the canvas.
60916      */
60917     setSize: function(w, h) {
60918         if (this.background) {
60919             this.background.setAttributes({
60920                 width: w,
60921                 height: h,
60922                 hidden: false
60923             }, true);
60924         }
60925         this.applyViewBox();
60926     },
60927
60928     // @private
60929     scrubAttrs: function(sprite) {
60930         var i,
60931             attrs = {},
60932             exclude = {},
60933             sattr = sprite.attr;
60934         for (i in sattr) {
60935             // Narrow down attributes to the main set
60936             if (this.translateAttrs.hasOwnProperty(i)) {
60937                 // Translated attr
60938                 attrs[this.translateAttrs[i]] = sattr[i];
60939                 exclude[this.translateAttrs[i]] = true;
60940             }
60941             else if (this.availableAttrs.hasOwnProperty(i) && !exclude[i]) {
60942                 // Passtrhough attr
60943                 attrs[i] = sattr[i];
60944             }
60945         }
60946         return attrs;
60947     },
60948
60949     // @private
60950     onClick: function(e) {
60951         this.processEvent('click', e);
60952     },
60953
60954     // @private
60955     onMouseUp: function(e) {
60956         this.processEvent('mouseup', e);
60957     },
60958
60959     // @private
60960     onMouseDown: function(e) {
60961         this.processEvent('mousedown', e);
60962     },
60963
60964     // @private
60965     onMouseOver: function(e) {
60966         this.processEvent('mouseover', e);
60967     },
60968
60969     // @private
60970     onMouseOut: function(e) {
60971         this.processEvent('mouseout', e);
60972     },
60973
60974     // @private
60975     onMouseMove: function(e) {
60976         this.fireEvent('mousemove', e);
60977     },
60978
60979     // @private
60980     onMouseEnter: Ext.emptyFn,
60981
60982     // @private
60983     onMouseLeave: Ext.emptyFn,
60984
60985     /**
60986      * Adds a gradient definition to the Surface. Note that in some surface engines, adding
60987      * a gradient via this method will not take effect if the surface has already been rendered.
60988      * Therefore, it is preferred to pass the gradients as an item to the surface config, rather
60989      * than calling this method, especially if the surface is rendered immediately (e.g. due to
60990      * 'renderTo' in its config). For more information on how to create gradients in the Chart
60991      * configuration object please refer to {@link Ext.chart.Chart}.
60992      *
60993      * The gradient object to be passed into this method is composed by:
60994      *
60995      * - **id** - string - The unique name of the gradient.
60996      * - **angle** - number, optional - The angle of the gradient in degrees.
60997      * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values.
60998      *
60999      * For example:
61000      *
61001      *    drawComponent.surface.addGradient({
61002      *        id: 'gradientId',
61003      *        angle: 45,
61004      *        stops: {
61005      *            0: {
61006      *                color: '#555'
61007      *            },
61008      *            100: {
61009      *                color: '#ddd'
61010      *            }
61011      *        }
61012      *    });
61013      *
61014      * @method
61015      */
61016     addGradient: Ext.emptyFn,
61017
61018     /**
61019      * Adds a Sprite to the surface. See {@link Ext.draw.Sprite} for the configuration object to be
61020      * passed into this method.
61021      *
61022      * For example:
61023      *
61024      *     drawComponent.surface.add({
61025      *         type: 'circle',
61026      *         fill: '#ffc',
61027      *         radius: 100,
61028      *         x: 100,
61029      *         y: 100
61030      *     });
61031      *
61032      */
61033     add: function() {
61034         var args = Array.prototype.slice.call(arguments),
61035             sprite,
61036             index;
61037
61038         var hasMultipleArgs = args.length > 1;
61039         if (hasMultipleArgs || Ext.isArray(args[0])) {
61040             var items = hasMultipleArgs ? args : args[0],
61041                 results = [],
61042                 i, ln, item;
61043
61044             for (i = 0, ln = items.length; i < ln; i++) {
61045                 item = items[i];
61046                 item = this.add(item);
61047                 results.push(item);
61048             }
61049
61050             return results;
61051         }
61052         sprite = this.prepareItems(args[0], true)[0];
61053         this.insertByZIndex(sprite);
61054         this.onAdd(sprite);
61055         return sprite;
61056     },
61057
61058     /**
61059      * @private
61060      * Inserts a given sprite into the correct position in the items collection, according to
61061      * its zIndex. It will be inserted at the end of an existing series of sprites with the same or
61062      * lower zIndex. By ensuring sprites are always ordered, this allows surface subclasses to render
61063      * the sprites in the correct order for proper z-index stacking.
61064      * @param {Ext.draw.Sprite} sprite
61065      * @return {Number} the sprite's new index in the list
61066      */
61067     insertByZIndex: function(sprite) {
61068         var me = this,
61069             sprites = me.items.items,
61070             len = sprites.length,
61071             ceil = Math.ceil,
61072             zIndex = sprite.attr.zIndex,
61073             idx = len,
61074             high = idx - 1,
61075             low = 0,
61076             otherZIndex;
61077
61078         if (me.orderSpritesByZIndex && len && zIndex < sprites[high].attr.zIndex) {
61079             // Find the target index via a binary search for speed
61080             while (low <= high) {
61081                 idx = ceil((low + high) / 2);
61082                 otherZIndex = sprites[idx].attr.zIndex;
61083                 if (otherZIndex > zIndex) {
61084                     high = idx - 1;
61085                 }
61086                 else if (otherZIndex < zIndex) {
61087                     low = idx + 1;
61088                 }
61089                 else {
61090                     break;
61091                 }
61092             }
61093             // Step forward to the end of a sequence of the same or lower z-index
61094             while (idx < len && sprites[idx].attr.zIndex <= zIndex) {
61095                 idx++;
61096             }
61097         }
61098
61099         me.items.insert(idx, sprite);
61100         return idx;
61101     },
61102
61103     onAdd: function(sprite) {
61104         var group = sprite.group,
61105             draggable = sprite.draggable,
61106             groups, ln, i;
61107         if (group) {
61108             groups = [].concat(group);
61109             ln = groups.length;
61110             for (i = 0; i < ln; i++) {
61111                 group = groups[i];
61112                 this.getGroup(group).add(sprite);
61113             }
61114             delete sprite.group;
61115         }
61116         if (draggable) {
61117             sprite.initDraggable();
61118         }
61119     },
61120
61121     /**
61122      * Removes a given sprite from the surface, optionally destroying the sprite in the process.
61123      * You can also call the sprite own `remove` method.
61124      *
61125      * For example:
61126      *
61127      *     drawComponent.surface.remove(sprite);
61128      *     //or...
61129      *     sprite.remove();
61130      *
61131      * @param {Ext.draw.Sprite} sprite
61132      * @param {Boolean} destroySprite
61133      * @return {Number} the sprite's new index in the list
61134      */
61135     remove: function(sprite, destroySprite) {
61136         if (sprite) {
61137             this.items.remove(sprite);
61138             this.groups.each(function(item) {
61139                 item.remove(sprite);
61140             });
61141             sprite.onRemove();
61142             if (destroySprite === true) {
61143                 sprite.destroy();
61144             }
61145         }
61146     },
61147
61148     /**
61149      * Removes all sprites from the surface, optionally destroying the sprites in the process.
61150      *
61151      * For example:
61152      *
61153      *     drawComponent.surface.removeAll();
61154      *
61155      * @param {Boolean} destroySprites Whether to destroy all sprites when removing them.
61156      * @return {Number} The sprite's new index in the list.
61157      */
61158     removeAll: function(destroySprites) {
61159         var items = this.items.items,
61160             ln = items.length,
61161             i;
61162         for (i = ln - 1; i > -1; i--) {
61163             this.remove(items[i], destroySprites);
61164         }
61165     },
61166
61167     onRemove: Ext.emptyFn,
61168
61169     onDestroy: Ext.emptyFn,
61170
61171     /**
61172      * @private Using the current viewBox property and the surface's width and height, calculate the
61173      * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
61174      */
61175     applyViewBox: function() {
61176         var me = this,
61177             viewBox = me.viewBox,
61178             width = me.width,
61179             height = me.height,
61180             viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
61181             relativeHeight, relativeWidth, size;
61182
61183         if (viewBox && (width || height)) {
61184             viewBoxX = viewBox.x;
61185             viewBoxY = viewBox.y;
61186             viewBoxWidth = viewBox.width;
61187             viewBoxHeight = viewBox.height;
61188             relativeHeight = height / viewBoxHeight;
61189             relativeWidth = width / viewBoxWidth;
61190
61191             if (viewBoxWidth * relativeHeight < width) {
61192                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
61193             }
61194             if (viewBoxHeight * relativeWidth < height) {
61195                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
61196             }
61197
61198             size = 1 / Math.min(viewBoxWidth, relativeHeight);
61199
61200             me.viewBoxShift = {
61201                 dx: -viewBoxX,
61202                 dy: -viewBoxY,
61203                 scale: size
61204             };
61205         }
61206     },
61207
61208     transformToViewBox: function (x, y) {
61209         if (this.viewBoxShift) {
61210             var me = this, shift = me.viewBoxShift;
61211             return [x * shift.scale - shift.dx, y * shift.scale - shift.dy];
61212         } else {
61213             return [x, y];
61214         }
61215     },
61216
61217     // @private
61218     applyTransformations: function(sprite) {
61219             sprite.bbox.transform = 0;
61220             this.transform(sprite);
61221
61222         var me = this,
61223             dirty = false,
61224             attr = sprite.attr;
61225
61226         if (attr.translation.x != null || attr.translation.y != null) {
61227             me.translate(sprite);
61228             dirty = true;
61229         }
61230         if (attr.scaling.x != null || attr.scaling.y != null) {
61231             me.scale(sprite);
61232             dirty = true;
61233         }
61234         if (attr.rotation.degrees != null) {
61235             me.rotate(sprite);
61236             dirty = true;
61237         }
61238         if (dirty) {
61239             sprite.bbox.transform = 0;
61240             this.transform(sprite);
61241             sprite.transformations = [];
61242         }
61243     },
61244
61245     // @private
61246     rotate: function (sprite) {
61247         var bbox,
61248             deg = sprite.attr.rotation.degrees,
61249             centerX = sprite.attr.rotation.x,
61250             centerY = sprite.attr.rotation.y;
61251         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
61252             bbox = this.getBBox(sprite);
61253             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
61254             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
61255         }
61256         sprite.transformations.push({
61257             type: "rotate",
61258             degrees: deg,
61259             x: centerX,
61260             y: centerY
61261         });
61262     },
61263
61264     // @private
61265     translate: function(sprite) {
61266         var x = sprite.attr.translation.x || 0,
61267             y = sprite.attr.translation.y || 0;
61268         sprite.transformations.push({
61269             type: "translate",
61270             x: x,
61271             y: y
61272         });
61273     },
61274
61275     // @private
61276     scale: function(sprite) {
61277         var bbox,
61278             x = sprite.attr.scaling.x || 1,
61279             y = sprite.attr.scaling.y || 1,
61280             centerX = sprite.attr.scaling.centerX,
61281             centerY = sprite.attr.scaling.centerY;
61282
61283         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
61284             bbox = this.getBBox(sprite);
61285             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
61286             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
61287         }
61288         sprite.transformations.push({
61289             type: "scale",
61290             x: x,
61291             y: y,
61292             centerX: centerX,
61293             centerY: centerY
61294         });
61295     },
61296
61297     // @private
61298     rectPath: function (x, y, w, h, r) {
61299         if (r) {
61300             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"]];
61301         }
61302         return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
61303     },
61304
61305     // @private
61306     ellipsePath: function (x, y, rx, ry) {
61307         if (ry == null) {
61308             ry = rx;
61309         }
61310         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"]];
61311     },
61312
61313     // @private
61314     getPathpath: function (el) {
61315         return el.attr.path;
61316     },
61317
61318     // @private
61319     getPathcircle: function (el) {
61320         var a = el.attr;
61321         return this.ellipsePath(a.x, a.y, a.radius, a.radius);
61322     },
61323
61324     // @private
61325     getPathellipse: function (el) {
61326         var a = el.attr;
61327         return this.ellipsePath(a.x, a.y,
61328                                 a.radiusX || (a.width / 2) || 0,
61329                                 a.radiusY || (a.height / 2) || 0);
61330     },
61331
61332     // @private
61333     getPathrect: function (el) {
61334         var a = el.attr;
61335         return this.rectPath(a.x, a.y, a.width, a.height, a.r);
61336     },
61337
61338     // @private
61339     getPathimage: function (el) {
61340         var a = el.attr;
61341         return this.rectPath(a.x || 0, a.y || 0, a.width, a.height);
61342     },
61343
61344     // @private
61345     getPathtext: function (el) {
61346         var bbox = this.getBBoxText(el);
61347         return this.rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
61348     },
61349
61350     createGroup: function(id) {
61351         var group = this.groups.get(id);
61352         if (!group) {
61353             group = Ext.create('Ext.draw.CompositeSprite', {
61354                 surface: this
61355             });
61356             group.id = id || Ext.id(null, 'ext-surface-group-');
61357             this.groups.add(group);
61358         }
61359         return group;
61360     },
61361
61362     /**
61363      * Returns a new group or an existent group associated with the current surface.
61364      * The group returned is a {@link Ext.draw.CompositeSprite} group.
61365      *
61366      * For example:
61367      *
61368      *     var spriteGroup = drawComponent.surface.getGroup('someGroupId');
61369      *
61370      * @param {String} id The unique identifier of the group.
61371      * @return {Object} The {@link Ext.draw.CompositeSprite}.
61372      */
61373     getGroup: function(id) {
61374         if (typeof id == "string") {
61375             var group = this.groups.get(id);
61376             if (!group) {
61377                 group = this.createGroup(id);
61378             }
61379         } else {
61380             group = id;
61381         }
61382         return group;
61383     },
61384
61385     // @private
61386     prepareItems: function(items, applyDefaults) {
61387         items = [].concat(items);
61388         // Make sure defaults are applied and item is initialized
61389         var item, i, ln;
61390         for (i = 0, ln = items.length; i < ln; i++) {
61391             item = items[i];
61392             if (!(item instanceof Ext.draw.Sprite)) {
61393                 // Temporary, just take in configs...
61394                 item.surface = this;
61395                 items[i] = this.createItem(item);
61396             } else {
61397                 item.surface = this;
61398             }
61399         }
61400         return items;
61401     },
61402
61403     /**
61404      * Changes the text in the sprite element. The sprite must be a `text` sprite.
61405      * This method can also be called from {@link Ext.draw.Sprite}.
61406      *
61407      * For example:
61408      *
61409      *     var spriteGroup = drawComponent.surface.setText(sprite, 'my new text');
61410      *
61411      * @param {Object} sprite The Sprite to change the text.
61412      * @param {String} text The new text to be set.
61413      * @method
61414      */
61415     setText: Ext.emptyFn,
61416
61417     //@private Creates an item and appends it to the surface. Called
61418     //as an internal method when calling `add`.
61419     createItem: Ext.emptyFn,
61420
61421     /**
61422      * Retrieves the id of this component.
61423      * Will autogenerate an id if one has not already been set.
61424      */
61425     getId: function() {
61426         return this.id || (this.id = Ext.id(null, 'ext-surface-'));
61427     },
61428
61429     /**
61430      * Destroys the surface. This is done by removing all components from it and
61431      * also removing its reference to a DOM element.
61432      *
61433      * For example:
61434      *
61435      *      drawComponent.surface.destroy();
61436      */
61437     destroy: function() {
61438         delete this.domRef;
61439         this.removeAll();
61440     }
61441 });
61442 /**
61443  * @class Ext.layout.component.Draw
61444  * @extends Ext.layout.component.Component
61445  * @private
61446  *
61447  */
61448
61449 Ext.define('Ext.layout.component.Draw', {
61450
61451     /* Begin Definitions */
61452
61453     alias: 'layout.draw',
61454
61455     extend: 'Ext.layout.component.Auto',
61456
61457     /* End Definitions */
61458
61459     type: 'draw',
61460
61461     onLayout : function(width, height) {
61462         this.owner.surface.setSize(width, height);
61463         this.callParent(arguments);
61464     }
61465 });
61466 /**
61467  * @class Ext.draw.Component
61468  * @extends Ext.Component
61469  *
61470  * The Draw Component is a surface in which sprites can be rendered. The Draw Component
61471  * manages and holds a `Surface` instance: an interface that has
61472  * an SVG or VML implementation depending on the browser capabilities and where
61473  * Sprites can be appended.
61474  *
61475  * One way to create a draw component is:
61476  *
61477  *     @example
61478  *     var drawComponent = Ext.create('Ext.draw.Component', {
61479  *         viewBox: false,
61480  *         items: [{
61481  *             type: 'circle',
61482  *             fill: '#79BB3F',
61483  *             radius: 100,
61484  *             x: 100,
61485  *             y: 100
61486  *         }]
61487  *     });
61488  *
61489  *     Ext.create('Ext.Window', {
61490  *         width: 215,
61491  *         height: 235,
61492  *         layout: 'fit',
61493  *         items: [drawComponent]
61494  *     }).show();
61495  *
61496  * In this case we created a draw component and added a sprite to it.
61497  * The *type* of the sprite is *circle* so if you run this code you'll see a yellow-ish
61498  * circle in a Window. When setting `viewBox` to `false` we are responsible for setting the object's position and
61499  * dimensions accordingly.
61500  *
61501  * You can also add sprites by using the surface's add method:
61502  *
61503  *     drawComponent.surface.add({
61504  *         type: 'circle',
61505  *         fill: '#79BB3F',
61506  *         radius: 100,
61507  *         x: 100,
61508  *         y: 100
61509  *     });
61510  *
61511  * For more information on Sprites, the core elements added to a draw component's surface,
61512  * refer to the Ext.draw.Sprite documentation.
61513  */
61514 Ext.define('Ext.draw.Component', {
61515
61516     /* Begin Definitions */
61517
61518     alias: 'widget.draw',
61519
61520     extend: 'Ext.Component',
61521
61522     requires: [
61523         'Ext.draw.Surface',
61524         'Ext.layout.component.Draw'
61525     ],
61526
61527     /* End Definitions */
61528
61529     /**
61530      * @cfg {String[]} enginePriority
61531      * Defines the priority order for which Surface implementation to use. The first
61532      * one supported by the current environment will be used.
61533      */
61534     enginePriority: ['Svg', 'Vml'],
61535
61536     baseCls: Ext.baseCSSPrefix + 'surface',
61537
61538     componentLayout: 'draw',
61539
61540     /**
61541      * @cfg {Boolean} viewBox
61542      * Turn on view box support which will scale and position items in the draw component to fit to the component while
61543      * maintaining aspect ratio. Note that this scaling can override other sizing settings on yor items. Defaults to true.
61544      */
61545     viewBox: true,
61546
61547     /**
61548      * @cfg {Boolean} autoSize
61549      * Turn on autoSize support which will set the bounding div's size to the natural size of the contents. Defaults to false.
61550      */
61551     autoSize: false,
61552
61553     /**
61554      * @cfg {Object[]} gradients (optional) Define a set of gradients that can be used as `fill` property in sprites.
61555      * The gradients array is an array of objects with the following properties:
61556      *
61557      *  - `id` - string - The unique name of the gradient.
61558      *  - `angle` - number, optional - The angle of the gradient in degrees.
61559      *  - `stops` - object - An object with numbers as keys (from 0 to 100) and style objects as values
61560      *
61561      * ## Example
61562      *
61563      *     gradients: [{
61564      *         id: 'gradientId',
61565      *         angle: 45,
61566      *         stops: {
61567      *             0: {
61568      *                 color: '#555'
61569      *             },
61570      *             100: {
61571      *                 color: '#ddd'
61572      *             }
61573      *         }
61574      *     }, {
61575      *         id: 'gradientId2',
61576      *         angle: 0,
61577      *         stops: {
61578      *             0: {
61579      *                 color: '#590'
61580      *             },
61581      *             20: {
61582      *                 color: '#599'
61583      *             },
61584      *             100: {
61585      *                 color: '#ddd'
61586      *             }
61587      *         }
61588      *     }]
61589      *
61590      * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
61591      *
61592      *     sprite.setAttributes({
61593      *         fill: 'url(#gradientId)'
61594      *     }, true);
61595      */
61596     initComponent: function() {
61597         this.callParent(arguments);
61598
61599         this.addEvents(
61600             'mousedown',
61601             'mouseup',
61602             'mousemove',
61603             'mouseenter',
61604             'mouseleave',
61605             'click'
61606         );
61607     },
61608
61609     /**
61610      * @private
61611      *
61612      * Create the Surface on initial render
61613      */
61614     onRender: function() {
61615         var me = this,
61616             viewBox = me.viewBox,
61617             autoSize = me.autoSize,
61618             bbox, items, width, height, x, y;
61619         me.callParent(arguments);
61620
61621         if (me.createSurface() !== false) {
61622             items = me.surface.items;
61623
61624             if (viewBox || autoSize) {
61625                 bbox = items.getBBox();
61626                 width = bbox.width;
61627                 height = bbox.height;
61628                 x = bbox.x;
61629                 y = bbox.y;
61630                 if (me.viewBox) {
61631                     me.surface.setViewBox(x, y, width, height);
61632                 }
61633                 else {
61634                     // AutoSized
61635                     me.autoSizeSurface();
61636                 }
61637             }
61638         }
61639     },
61640
61641     //@private
61642     autoSizeSurface: function() {
61643         var me = this,
61644             items = me.surface.items,
61645             bbox = items.getBBox(),
61646             width = bbox.width,
61647             height = bbox.height;
61648         items.setAttributes({
61649             translate: {
61650                 x: -bbox.x,
61651                 //Opera has a slight offset in the y axis.
61652                 y: -bbox.y + (+Ext.isOpera)
61653             }
61654         }, true);
61655         if (me.rendered) {
61656             me.setSize(width, height);
61657             me.surface.setSize(width, height);
61658         }
61659         else {
61660             me.surface.setSize(width, height);
61661         }
61662         me.el.setSize(width, height);
61663     },
61664
61665     /**
61666      * Create the Surface instance. Resolves the correct Surface implementation to
61667      * instantiate based on the 'enginePriority' config. Once the Surface instance is
61668      * created you can use the handle to that instance to add sprites. For example:
61669      *
61670      *     drawComponent.surface.add(sprite);
61671      */
61672     createSurface: function() {
61673         var surface = Ext.draw.Surface.create(Ext.apply({}, {
61674                 width: this.width,
61675                 height: this.height,
61676                 renderTo: this.el
61677             }, this.initialConfig));
61678         if (!surface) {
61679             // In case we cannot create a surface, return false so we can stop
61680             return false;
61681         }
61682         this.surface = surface;
61683
61684
61685         function refire(eventName) {
61686             return function(e) {
61687                 this.fireEvent(eventName, e);
61688             };
61689         }
61690
61691         surface.on({
61692             scope: this,
61693             mouseup: refire('mouseup'),
61694             mousedown: refire('mousedown'),
61695             mousemove: refire('mousemove'),
61696             mouseenter: refire('mouseenter'),
61697             mouseleave: refire('mouseleave'),
61698             click: refire('click')
61699         });
61700     },
61701
61702
61703     /**
61704      * @private
61705      *
61706      * Clean up the Surface instance on component destruction
61707      */
61708     onDestroy: function() {
61709         var surface = this.surface;
61710         if (surface) {
61711             surface.destroy();
61712         }
61713         this.callParent(arguments);
61714     }
61715
61716 });
61717
61718 /**
61719  * @class Ext.chart.LegendItem
61720  * @extends Ext.draw.CompositeSprite
61721  * A single item of a legend (marker plus label)
61722  */
61723 Ext.define('Ext.chart.LegendItem', {
61724
61725     /* Begin Definitions */
61726
61727     extend: 'Ext.draw.CompositeSprite',
61728
61729     requires: ['Ext.chart.Shape'],
61730
61731     /* End Definitions */
61732
61733     // Position of the item, relative to the upper-left corner of the legend box
61734     x: 0,
61735     y: 0,
61736     zIndex: 500,
61737
61738     constructor: function(config) {
61739         this.callParent(arguments);
61740         this.createLegend(config);
61741     },
61742
61743     /**
61744      * Creates all the individual sprites for this legend item
61745      */
61746     createLegend: function(config) {
61747         var me = this,
61748             index = config.yFieldIndex,
61749             series = me.series,
61750             seriesType = series.type,
61751             idx = me.yFieldIndex,
61752             legend = me.legend,
61753             surface = me.surface,
61754             refX = legend.x + me.x,
61755             refY = legend.y + me.y,
61756             bbox, z = me.zIndex,
61757             markerConfig, label, mask,
61758             radius, toggle = false,
61759             seriesStyle = Ext.apply(series.seriesStyle, series.style);
61760
61761         function getSeriesProp(name) {
61762             var val = series[name];
61763             return (Ext.isArray(val) ? val[idx] : val);
61764         }
61765         
61766         label = me.add('label', surface.add({
61767             type: 'text',
61768             x: 20,
61769             y: 0,
61770             zIndex: z || 0,
61771             font: legend.labelFont,
61772             text: getSeriesProp('title') || getSeriesProp('yField')
61773         }));
61774
61775         // Line series - display as short line with optional marker in the middle
61776         if (seriesType === 'line' || seriesType === 'scatter') {
61777             if(seriesType === 'line') {
61778                 me.add('line', surface.add({
61779                     type: 'path',
61780                     path: 'M0.5,0.5L16.5,0.5',
61781                     zIndex: z,
61782                     "stroke-width": series.lineWidth,
61783                     "stroke-linejoin": "round",
61784                     "stroke-dasharray": series.dash,
61785                     stroke: seriesStyle.stroke || '#000',
61786                     style: {
61787                         cursor: 'pointer'
61788                     }
61789                 }));
61790             }
61791             if (series.showMarkers || seriesType === 'scatter') {
61792                 markerConfig = Ext.apply(series.markerStyle, series.markerConfig || {});
61793                 me.add('marker', Ext.chart.Shape[markerConfig.type](surface, {
61794                     fill: markerConfig.fill,
61795                     x: 8.5,
61796                     y: 0.5,
61797                     zIndex: z,
61798                     radius: markerConfig.radius || markerConfig.size,
61799                     style: {
61800                         cursor: 'pointer'
61801                     }
61802                 }));
61803             }
61804         }
61805         // All other series types - display as filled box
61806         else {
61807             me.add('box', surface.add({
61808                 type: 'rect',
61809                 zIndex: z,
61810                 x: 0,
61811                 y: 0,
61812                 width: 12,
61813                 height: 12,
61814                 fill: series.getLegendColor(index),
61815                 style: {
61816                     cursor: 'pointer'
61817                 }
61818             }));
61819         }
61820         
61821         me.setAttributes({
61822             hidden: false
61823         }, true);
61824         
61825         bbox = me.getBBox();
61826         
61827         mask = me.add('mask', surface.add({
61828             type: 'rect',
61829             x: bbox.x,
61830             y: bbox.y,
61831             width: bbox.width || 20,
61832             height: bbox.height || 20,
61833             zIndex: (z || 0) + 1000,
61834             fill: '#f00',
61835             opacity: 0,
61836             style: {
61837                 'cursor': 'pointer'
61838             }
61839         }));
61840
61841         //add toggle listener
61842         me.on('mouseover', function() {
61843             label.setStyle({
61844                 'font-weight': 'bold'
61845             });
61846             mask.setStyle({
61847                 'cursor': 'pointer'
61848             });
61849             series._index = index;
61850             series.highlightItem();
61851         }, me);
61852
61853         me.on('mouseout', function() {
61854             label.setStyle({
61855                 'font-weight': 'normal'
61856             });
61857             series._index = index;
61858             series.unHighlightItem();
61859         }, me);
61860         
61861         if (!series.visibleInLegend(index)) {
61862             toggle = true;
61863             label.setAttributes({
61864                opacity: 0.5
61865             }, true);
61866         }
61867
61868         me.on('mousedown', function() {
61869             if (!toggle) {
61870                 series.hideAll();
61871                 label.setAttributes({
61872                     opacity: 0.5
61873                 }, true);
61874             } else {
61875                 series.showAll();
61876                 label.setAttributes({
61877                     opacity: 1
61878                 }, true);
61879             }
61880             toggle = !toggle;
61881         }, me);
61882         me.updatePosition({x:0, y:0}); //Relative to 0,0 at first so that the bbox is calculated correctly
61883     },
61884
61885     /**
61886      * Update the positions of all this item's sprites to match the root position
61887      * of the legend box.
61888      * @param {Object} relativeTo (optional) If specified, this object's 'x' and 'y' values will be used
61889      *                 as the reference point for the relative positioning. Defaults to the Legend.
61890      */
61891     updatePosition: function(relativeTo) {
61892         var me = this,
61893             items = me.items,
61894             ln = items.length,
61895             i = 0,
61896             item;
61897         if (!relativeTo) {
61898             relativeTo = me.legend;
61899         }
61900         for (; i < ln; i++) {
61901             item = items[i];
61902             switch (item.type) {
61903                 case 'text':
61904                     item.setAttributes({
61905                         x: 20 + relativeTo.x + me.x,
61906                         y: relativeTo.y + me.y
61907                     }, true);
61908                     break;
61909                 case 'rect':
61910                     item.setAttributes({
61911                         translate: {
61912                             x: relativeTo.x + me.x,
61913                             y: relativeTo.y + me.y - 6
61914                         }
61915                     }, true);
61916                     break;
61917                 default:
61918                     item.setAttributes({
61919                         translate: {
61920                             x: relativeTo.x + me.x,
61921                             y: relativeTo.y + me.y
61922                         }
61923                     }, true);
61924             }
61925         }
61926     }
61927 });
61928
61929 /**
61930  * @class Ext.chart.Legend
61931  *
61932  * Defines a legend for a chart's series.
61933  * The 'chart' member must be set prior to rendering.
61934  * The legend class displays a list of legend items each of them related with a
61935  * series being rendered. In order to render the legend item of the proper series
61936  * the series configuration object must have `showInSeries` set to true.
61937  *
61938  * The legend configuration object accepts a `position` as parameter.
61939  * The `position` parameter can be `left`, `right`
61940  * `top` or `bottom`. For example:
61941  *
61942  *     legend: {
61943  *         position: 'right'
61944  *     },
61945  *
61946  * ## Example
61947  *
61948  *     @example
61949  *     var store = Ext.create('Ext.data.JsonStore', {
61950  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
61951  *         data: [
61952  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
61953  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
61954  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
61955  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
61956  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
61957  *         ]
61958  *     });
61959  *
61960  *     Ext.create('Ext.chart.Chart', {
61961  *         renderTo: Ext.getBody(),
61962  *         width: 500,
61963  *         height: 300,
61964  *         animate: true,
61965  *         store: store,
61966  *         shadow: true,
61967  *         theme: 'Category1',
61968  *         legend: {
61969  *             position: 'top'
61970  *         },
61971  *         axes: [
61972  *             {
61973  *                 type: 'Numeric',
61974  *                 grid: true,
61975  *                 position: 'left',
61976  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
61977  *                 title: 'Sample Values',
61978  *                 grid: {
61979  *                     odd: {
61980  *                         opacity: 1,
61981  *                         fill: '#ddd',
61982  *                         stroke: '#bbb',
61983  *                         'stroke-width': 1
61984  *                     }
61985  *                 },
61986  *                 minimum: 0,
61987  *                 adjustMinimumByMajorUnit: 0
61988  *             },
61989  *             {
61990  *                 type: 'Category',
61991  *                 position: 'bottom',
61992  *                 fields: ['name'],
61993  *                 title: 'Sample Metrics',
61994  *                 grid: true,
61995  *                 label: {
61996  *                     rotate: {
61997  *                         degrees: 315
61998  *                     }
61999  *                 }
62000  *             }
62001  *         ],
62002  *         series: [{
62003  *             type: 'area',
62004  *             highlight: false,
62005  *             axis: 'left',
62006  *             xField: 'name',
62007  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
62008  *             style: {
62009  *                 opacity: 0.93
62010  *             }
62011  *         }]
62012  *     });
62013  */
62014 Ext.define('Ext.chart.Legend', {
62015
62016     /* Begin Definitions */
62017
62018     requires: ['Ext.chart.LegendItem'],
62019
62020     /* End Definitions */
62021
62022     /**
62023      * @cfg {Boolean} visible
62024      * Whether or not the legend should be displayed.
62025      */
62026     visible: true,
62027
62028     /**
62029      * @cfg {String} position
62030      * The position of the legend in relation to the chart. One of: "top",
62031      * "bottom", "left", "right", or "float". If set to "float", then the legend
62032      * box will be positioned at the point denoted by the x and y parameters.
62033      */
62034     position: 'bottom',
62035
62036     /**
62037      * @cfg {Number} x
62038      * X-position of the legend box. Used directly if position is set to "float", otherwise
62039      * it will be calculated dynamically.
62040      */
62041     x: 0,
62042
62043     /**
62044      * @cfg {Number} y
62045      * Y-position of the legend box. Used directly if position is set to "float", otherwise
62046      * it will be calculated dynamically.
62047      */
62048     y: 0,
62049
62050     /**
62051      * @cfg {String} labelFont
62052      * Font to be used for the legend labels, eg '12px Helvetica'
62053      */
62054     labelFont: '12px Helvetica, sans-serif',
62055
62056     /**
62057      * @cfg {String} boxStroke
62058      * Style of the stroke for the legend box
62059      */
62060     boxStroke: '#000',
62061
62062     /**
62063      * @cfg {String} boxStrokeWidth
62064      * Width of the stroke for the legend box
62065      */
62066     boxStrokeWidth: 1,
62067
62068     /**
62069      * @cfg {String} boxFill
62070      * Fill style for the legend box
62071      */
62072     boxFill: '#FFF',
62073
62074     /**
62075      * @cfg {Number} itemSpacing
62076      * Amount of space between legend items
62077      */
62078     itemSpacing: 10,
62079
62080     /**
62081      * @cfg {Number} padding
62082      * Amount of padding between the legend box's border and its items
62083      */
62084     padding: 5,
62085
62086     // @private
62087     width: 0,
62088     // @private
62089     height: 0,
62090
62091     /**
62092      * @cfg {Number} boxZIndex
62093      * Sets the z-index for the legend. Defaults to 100.
62094      */
62095     boxZIndex: 100,
62096
62097     /**
62098      * Creates new Legend.
62099      * @param {Object} config  (optional) Config object.
62100      */
62101     constructor: function(config) {
62102         var me = this;
62103         if (config) {
62104             Ext.apply(me, config);
62105         }
62106         me.items = [];
62107         /**
62108          * Whether the legend box is oriented vertically, i.e. if it is on the left or right side or floating.
62109          * @type {Boolean}
62110          */
62111         me.isVertical = ("left|right|float".indexOf(me.position) !== -1);
62112
62113         // cache these here since they may get modified later on
62114         me.origX = me.x;
62115         me.origY = me.y;
62116     },
62117
62118     /**
62119      * @private Create all the sprites for the legend
62120      */
62121     create: function() {
62122         var me = this;
62123         me.createBox();
62124         me.createItems();
62125         if (!me.created && me.isDisplayed()) {
62126             me.created = true;
62127
62128             // Listen for changes to series titles to trigger regeneration of the legend
62129             me.chart.series.each(function(series) {
62130                 series.on('titlechange', function() {
62131                     me.create();
62132                     me.updatePosition();
62133                 });
62134             });
62135         }
62136     },
62137
62138     /**
62139      * @private Determine whether the legend should be displayed. Looks at the legend's 'visible' config,
62140      * and also the 'showInLegend' config for each of the series.
62141      */
62142     isDisplayed: function() {
62143         return this.visible && this.chart.series.findIndex('showInLegend', true) !== -1;
62144     },
62145
62146     /**
62147      * @private Create the series markers and labels
62148      */
62149     createItems: function() {
62150         var me = this,
62151             chart = me.chart,
62152             surface = chart.surface,
62153             items = me.items,
62154             padding = me.padding,
62155             itemSpacing = me.itemSpacing,
62156             spacingOffset = 2,
62157             maxWidth = 0,
62158             maxHeight = 0,
62159             totalWidth = 0,
62160             totalHeight = 0,
62161             vertical = me.isVertical,
62162             math = Math,
62163             mfloor = math.floor,
62164             mmax = math.max,
62165             index = 0,
62166             i = 0,
62167             len = items ? items.length : 0,
62168             x, y, spacing, item, bbox, height, width;
62169
62170         //remove all legend items
62171         if (len) {
62172             for (; i < len; i++) {
62173                 items[i].destroy();
62174             }
62175         }
62176         //empty array
62177         items.length = [];
62178         // Create all the item labels, collecting their dimensions and positioning each one
62179         // properly in relation to the previous item
62180         chart.series.each(function(series, i) {
62181             if (series.showInLegend) {
62182                 Ext.each([].concat(series.yField), function(field, j) {
62183                     item = Ext.create('Ext.chart.LegendItem', {
62184                         legend: this,
62185                         series: series,
62186                         surface: chart.surface,
62187                         yFieldIndex: j
62188                     });
62189                     bbox = item.getBBox();
62190
62191                     //always measure from x=0, since not all markers go all the way to the left
62192                     width = bbox.width;
62193                     height = bbox.height;
62194
62195                     if (i + j === 0) {
62196                         spacing = vertical ? padding + height / 2 : padding;
62197                     }
62198                     else {
62199                         spacing = itemSpacing / (vertical ? 2 : 1);
62200                     }
62201                     // Set the item's position relative to the legend box
62202                     item.x = mfloor(vertical ? padding : totalWidth + spacing);
62203                     item.y = mfloor(vertical ? totalHeight + spacing : padding + height / 2);
62204
62205                     // Collect cumulative dimensions
62206                     totalWidth += width + spacing;
62207                     totalHeight += height + spacing;
62208                     maxWidth = mmax(maxWidth, width);
62209                     maxHeight = mmax(maxHeight, height);
62210
62211                     items.push(item);
62212                 }, this);
62213             }
62214         }, me);
62215
62216         // Store the collected dimensions for later
62217         me.width = mfloor((vertical ? maxWidth : totalWidth) + padding * 2);
62218         if (vertical && items.length === 1) {
62219             spacingOffset = 1;
62220         }
62221         me.height = mfloor((vertical ? totalHeight - spacingOffset * spacing : maxHeight) + (padding * 2));
62222         me.itemHeight = maxHeight;
62223     },
62224
62225     /**
62226      * @private Get the bounds for the legend's outer box
62227      */
62228     getBBox: function() {
62229         var me = this;
62230         return {
62231             x: Math.round(me.x) - me.boxStrokeWidth / 2,
62232             y: Math.round(me.y) - me.boxStrokeWidth / 2,
62233             width: me.width,
62234             height: me.height
62235         };
62236     },
62237
62238     /**
62239      * @private Create the box around the legend items
62240      */
62241     createBox: function() {
62242         var me = this,
62243             box;
62244
62245         if (me.boxSprite) {
62246             me.boxSprite.destroy();
62247         }
62248         
62249         box = me.boxSprite = me.chart.surface.add(Ext.apply({
62250             type: 'rect',
62251             stroke: me.boxStroke,
62252             "stroke-width": me.boxStrokeWidth,
62253             fill: me.boxFill,
62254             zIndex: me.boxZIndex
62255         }, me.getBBox()));
62256
62257         box.redraw();
62258     },
62259
62260     /**
62261      * @private Update the position of all the legend's sprites to match its current x/y values
62262      */
62263     updatePosition: function() {
62264         var me = this,
62265             x, y,
62266             legendWidth = me.width,
62267             legendHeight = me.height,
62268             padding = me.padding,
62269             chart = me.chart,
62270             chartBBox = chart.chartBBox,
62271             insets = chart.insetPadding,
62272             chartWidth = chartBBox.width - (insets * 2),
62273             chartHeight = chartBBox.height - (insets * 2),
62274             chartX = chartBBox.x + insets,
62275             chartY = chartBBox.y + insets,
62276             surface = chart.surface,
62277             mfloor = Math.floor;
62278
62279         if (me.isDisplayed()) {
62280             // Find the position based on the dimensions
62281             switch(me.position) {
62282                 case "left":
62283                     x = insets;
62284                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
62285                     break;
62286                 case "right":
62287                     x = mfloor(surface.width - legendWidth) - insets;
62288                     y = mfloor(chartY + chartHeight / 2 - legendHeight / 2);
62289                     break;
62290                 case "top":
62291                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
62292                     y = insets;
62293                     break;
62294                 case "bottom":
62295                     x = mfloor(chartX + chartWidth / 2 - legendWidth / 2);
62296                     y = mfloor(surface.height - legendHeight) - insets;
62297                     break;
62298                 default:
62299                     x = mfloor(me.origX) + insets;
62300                     y = mfloor(me.origY) + insets;
62301             }
62302             me.x = x;
62303             me.y = y;
62304
62305             // Update the position of each item
62306             Ext.each(me.items, function(item) {
62307                 item.updatePosition();
62308             });
62309             // Update the position of the outer box
62310             me.boxSprite.setAttributes(me.getBBox(), true);
62311         }
62312     }
62313 });
62314
62315 /**
62316  * Charts provide a flexible way to achieve a wide range of data visualization capablitities.
62317  * Each Chart gets its data directly from a {@link Ext.data.Store Store}, and automatically
62318  * updates its display whenever data in the Store changes. In addition, the look and feel
62319  * of a Chart can be customized using {@link Ext.chart.theme.Theme Theme}s.
62320  * 
62321  * ## Creating a Simple Chart
62322  * 
62323  * Every Chart has three key parts - a {@link Ext.data.Store Store} that contains the data,
62324  * an array of {@link Ext.chart.axis.Axis Axes} which define the boundaries of the Chart,
62325  * and one or more {@link Ext.chart.series.Series Series} to handle the visual rendering of the data points.
62326  * 
62327  * ### 1. Creating a Store
62328  * 
62329  * The first step is to create a {@link Ext.data.Model Model} that represents the type of
62330  * data that will be displayed in the Chart. For example the data for a chart that displays
62331  * a weather forecast could be represented as a series of "WeatherPoint" data points with
62332  * two fields - "temperature", and "date":
62333  * 
62334  *     Ext.define('WeatherPoint', {
62335  *         extend: 'Ext.data.Model',
62336  *         fields: ['temperature', 'date']
62337  *     });
62338  * 
62339  * Next a {@link Ext.data.Store Store} must be created.  The store contains a collection of "WeatherPoint" Model instances.
62340  * The data could be loaded dynamically, but for sake of ease this example uses inline data:
62341  * 
62342  *     var store = Ext.create('Ext.data.Store', {
62343  *         model: 'WeatherPoint',
62344  *         data: [
62345  *             { temperature: 58, date: new Date(2011, 1, 1, 8) },
62346  *             { temperature: 63, date: new Date(2011, 1, 1, 9) },
62347  *             { temperature: 73, date: new Date(2011, 1, 1, 10) },
62348  *             { temperature: 78, date: new Date(2011, 1, 1, 11) },
62349  *             { temperature: 81, date: new Date(2011, 1, 1, 12) }
62350  *         ]
62351  *     });
62352  *    
62353  * For additional information on Models and Stores please refer to the [Data Guide](#/guide/data).
62354  * 
62355  * ### 2. Creating the Chart object
62356  * 
62357  * Now that a Store has been created it can be used in a Chart:
62358  * 
62359  *     Ext.create('Ext.chart.Chart', {
62360  *        renderTo: Ext.getBody(),
62361  *        width: 400,
62362  *        height: 300,
62363  *        store: store
62364  *     });
62365  *    
62366  * That's all it takes to create a Chart instance that is backed by a Store.
62367  * However, if the above code is run in a browser, a blank screen will be displayed.
62368  * This is because the two pieces that are responsible for the visual display,
62369  * the Chart's {@link #cfg-axes axes} and {@link #cfg-series series}, have not yet been defined.
62370  * 
62371  * ### 3. Configuring the Axes
62372  * 
62373  * {@link Ext.chart.axis.Axis Axes} are the lines that define the boundaries of the data points that a Chart can display.
62374  * This example uses one of the most common Axes configurations - a horizontal "x" axis, and a vertical "y" axis:
62375  * 
62376  *     Ext.create('Ext.chart.Chart', {
62377  *         ...
62378  *         axes: [
62379  *             {
62380  *                 title: 'Temperature',
62381  *                 type: 'Numeric',
62382  *                 position: 'left',
62383  *                 fields: ['temperature'],
62384  *                 minimum: 0,
62385  *                 maximum: 100
62386  *             },
62387  *             {
62388  *                 title: 'Time',
62389  *                 type: 'Time',
62390  *                 position: 'bottom',
62391  *                 fields: ['date'],
62392  *                 dateFormat: 'ga'
62393  *             }
62394  *         ]
62395  *     });
62396  *    
62397  * The "Temperature" axis is a vertical {@link Ext.chart.axis.Numeric Numeric Axis} and is positioned on the left edge of the Chart.
62398  * It represents the bounds of the data contained in the "WeatherPoint" Model's "temperature" field that was
62399  * defined above. The minimum value for this axis is "0", and the maximum is "100".
62400  * 
62401  * The horizontal axis is a {@link Ext.chart.axis.Time Time Axis} and is positioned on the bottom edge of the Chart.
62402  * It represents the bounds of the data contained in the "WeatherPoint" Model's "date" field.
62403  * The {@link Ext.chart.axis.Time#cfg-dateFormat dateFormat}
62404  * configuration tells the Time Axis how to format it's labels.
62405  * 
62406  * Here's what the Chart looks like now that it has its Axes configured:
62407  * 
62408  * {@img Ext.chart.Chart/Ext.chart.Chart1.png Chart Axes}
62409  * 
62410  * ### 4. Configuring the Series
62411  * 
62412  * The final step in creating a simple Chart is to configure one or more {@link Ext.chart.series.Series Series}.
62413  * Series are responsible for the visual representation of the data points contained in the Store.
62414  * This example only has one Series:
62415  * 
62416  *     Ext.create('Ext.chart.Chart', {
62417  *         ...
62418  *         axes: [
62419  *             ...
62420  *         ],
62421  *         series: [
62422  *             {
62423  *                 type: 'line',
62424  *                 xField: 'date',
62425  *                 yField: 'temperature'
62426  *             }
62427  *         ]
62428  *     });
62429  *     
62430  * This Series is a {@link Ext.chart.series.Line Line Series}, and it uses the "date" and "temperature" fields
62431  * from the "WeatherPoint" Models in the Store to plot its data points:
62432  * 
62433  * {@img Ext.chart.Chart/Ext.chart.Chart2.png Line Series}
62434  * 
62435  * See the [Simple Chart Example](doc-resources/Ext.chart.Chart/examples/simple_chart/index.html) for a live demo.
62436  * 
62437  * ## Themes
62438  * 
62439  * The color scheme for a Chart can be easily changed using the {@link #cfg-theme theme} configuration option:
62440  * 
62441  *     Ext.create('Ext.chart.Chart', {
62442  *         ...
62443  *         theme: 'Green',
62444  *         ...
62445  *     });
62446  * 
62447  * {@img Ext.chart.Chart/Ext.chart.Chart3.png Green Theme}
62448  * 
62449  * For more information on Charts please refer to the [Drawing and Charting Guide](#/guide/drawing_and_charting).
62450  * 
62451  */
62452 Ext.define('Ext.chart.Chart', {
62453
62454     /* Begin Definitions */
62455
62456     alias: 'widget.chart',
62457
62458     extend: 'Ext.draw.Component',
62459     
62460     mixins: {
62461         themeManager: 'Ext.chart.theme.Theme',
62462         mask: 'Ext.chart.Mask',
62463         navigation: 'Ext.chart.Navigation'
62464     },
62465
62466     requires: [
62467         'Ext.util.MixedCollection',
62468         'Ext.data.StoreManager',
62469         'Ext.chart.Legend',
62470         'Ext.util.DelayedTask'
62471     ],
62472
62473     /* End Definitions */
62474
62475     // @private
62476     viewBox: false,
62477
62478     /**
62479      * @cfg {String} theme
62480      * The name of the theme to be used. A theme defines the colors and other visual displays of tick marks
62481      * on axis, text, title text, line colors, marker colors and styles, etc. Possible theme values are 'Base', 'Green',
62482      * 'Sky', 'Red', 'Purple', 'Blue', 'Yellow' and also six category themes 'Category1' to 'Category6'. Default value
62483      * is 'Base'.
62484      */
62485
62486     /**
62487      * @cfg {Boolean/Object} animate
62488      * True for the default animation (easing: 'ease' and duration: 500) or a standard animation config
62489      * object to be used for default chart animations. Defaults to false.
62490      */
62491     animate: false,
62492
62493     /**
62494      * @cfg {Boolean/Object} legend
62495      * True for the default legend display or a legend config object. Defaults to false.
62496      */
62497     legend: false,
62498
62499     /**
62500      * @cfg {Number} insetPadding
62501      * The amount of inset padding in pixels for the chart. Defaults to 10.
62502      */
62503     insetPadding: 10,
62504
62505     /**
62506      * @cfg {String[]} enginePriority
62507      * Defines the priority order for which Surface implementation to use. The first one supported by the current
62508      * environment will be used. Defaults to `['Svg', 'Vml']`.
62509      */
62510     enginePriority: ['Svg', 'Vml'],
62511
62512     /**
62513      * @cfg {Object/Boolean} background
62514      * The chart background. This can be a gradient object, image, or color. Defaults to false for no
62515      * background. For example, if `background` were to be a color we could set the object as
62516      *
62517      *     background: {
62518      *         //color string
62519      *         fill: '#ccc'
62520      *     }
62521      *
62522      * You can specify an image by using:
62523      *
62524      *     background: {
62525      *         image: 'http://path.to.image/'
62526      *     }
62527      *
62528      * Also you can specify a gradient by using the gradient object syntax:
62529      *
62530      *     background: {
62531      *         gradient: {
62532      *             id: 'gradientId',
62533      *             angle: 45,
62534      *             stops: {
62535      *                 0: {
62536      *                     color: '#555'
62537      *                 }
62538      *                 100: {
62539      *                     color: '#ddd'
62540      *                 }
62541      *             }
62542      *         }
62543      *     }
62544      */
62545     background: false,
62546
62547     /**
62548      * @cfg {Object[]} gradients
62549      * Define a set of gradients that can be used as `fill` property in sprites. The gradients array is an
62550      * array of objects with the following properties:
62551      *
62552      * - **id** - string - The unique name of the gradient.
62553      * - **angle** - number, optional - The angle of the gradient in degrees.
62554      * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values
62555      *
62556      * For example:
62557      *
62558      *     gradients: [{
62559      *         id: 'gradientId',
62560      *         angle: 45,
62561      *         stops: {
62562      *             0: {
62563      *                 color: '#555'
62564      *             },
62565      *             100: {
62566      *                 color: '#ddd'
62567      *             }
62568      *         }
62569      *     }, {
62570      *         id: 'gradientId2',
62571      *         angle: 0,
62572      *         stops: {
62573      *             0: {
62574      *                 color: '#590'
62575      *             },
62576      *             20: {
62577      *                 color: '#599'
62578      *             },
62579      *             100: {
62580      *                 color: '#ddd'
62581      *             }
62582      *         }
62583      *     }]
62584      *
62585      * Then the sprites can use `gradientId` and `gradientId2` by setting the fill attributes to those ids, for example:
62586      *
62587      *     sprite.setAttributes({
62588      *         fill: 'url(#gradientId)'
62589      *     }, true);
62590      */
62591
62592     /**
62593      * @cfg {Ext.data.Store} store
62594      * The store that supplies data to this chart.
62595      */
62596
62597     /**
62598      * @cfg {Ext.chart.series.Series[]} series
62599      * Array of {@link Ext.chart.series.Series Series} instances or config objects.  For example:
62600      * 
62601      *     series: [{
62602      *         type: 'column',
62603      *         axis: 'left',
62604      *         listeners: {
62605      *             'afterrender': function() {
62606      *                 console('afterrender');
62607      *             }
62608      *         },
62609      *         xField: 'category',
62610      *         yField: 'data1'
62611      *     }]
62612      */
62613
62614     /**
62615      * @cfg {Ext.chart.axis.Axis[]} axes
62616      * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects.  For example:
62617      * 
62618      *     axes: [{
62619      *         type: 'Numeric',
62620      *         position: 'left',
62621      *         fields: ['data1'],
62622      *         title: 'Number of Hits',
62623      *         minimum: 0,
62624      *         //one minor tick between two major ticks
62625      *         minorTickSteps: 1
62626      *     }, {
62627      *         type: 'Category',
62628      *         position: 'bottom',
62629      *         fields: ['name'],
62630      *         title: 'Month of the Year'
62631      *     }]
62632      */
62633
62634     constructor: function(config) {
62635         var me = this,
62636             defaultAnim;
62637             
62638         config = Ext.apply({}, config);
62639         me.initTheme(config.theme || me.theme);
62640         if (me.gradients) {
62641             Ext.apply(config, { gradients: me.gradients });
62642         }
62643         if (me.background) {
62644             Ext.apply(config, { background: me.background });
62645         }
62646         if (config.animate) {
62647             defaultAnim = {
62648                 easing: 'ease',
62649                 duration: 500
62650             };
62651             if (Ext.isObject(config.animate)) {
62652                 config.animate = Ext.applyIf(config.animate, defaultAnim);
62653             }
62654             else {
62655                 config.animate = defaultAnim;
62656             }
62657         }
62658         me.mixins.mask.constructor.call(me, config);
62659         me.mixins.navigation.constructor.call(me, config);
62660         me.callParent([config]);
62661     },
62662     
62663     getChartStore: function(){
62664         return this.substore || this.store;    
62665     },
62666
62667     initComponent: function() {
62668         var me = this,
62669             axes,
62670             series;
62671         me.callParent();
62672         me.addEvents(
62673             'itemmousedown',
62674             'itemmouseup',
62675             'itemmouseover',
62676             'itemmouseout',
62677             'itemclick',
62678             'itemdoubleclick',
62679             'itemdragstart',
62680             'itemdrag',
62681             'itemdragend',
62682             /**
62683              * @event beforerefresh
62684              * Fires before a refresh to the chart data is called. If the beforerefresh handler returns false the
62685              * {@link #refresh} action will be cancelled.
62686              * @param {Ext.chart.Chart} this
62687              */
62688             'beforerefresh',
62689             /**
62690              * @event refresh
62691              * Fires after the chart data has been refreshed.
62692              * @param {Ext.chart.Chart} this
62693              */
62694             'refresh'
62695         );
62696         Ext.applyIf(me, {
62697             zoom: {
62698                 width: 1,
62699                 height: 1,
62700                 x: 0,
62701                 y: 0
62702             }
62703         });
62704         me.maxGutter = [0, 0];
62705         me.store = Ext.data.StoreManager.lookup(me.store);
62706         axes = me.axes;
62707         me.axes = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.position; });
62708         if (axes) {
62709             me.axes.addAll(axes);
62710         }
62711         series = me.series;
62712         me.series = Ext.create('Ext.util.MixedCollection', false, function(a) { return a.seriesId || (a.seriesId = Ext.id(null, 'ext-chart-series-')); });
62713         if (series) {
62714             me.series.addAll(series);
62715         }
62716         if (me.legend !== false) {
62717             me.legend = Ext.create('Ext.chart.Legend', Ext.applyIf({chart:me}, me.legend));
62718         }
62719
62720         me.on({
62721             mousemove: me.onMouseMove,
62722             mouseleave: me.onMouseLeave,
62723             mousedown: me.onMouseDown,
62724             mouseup: me.onMouseUp,
62725             scope: me
62726         });
62727     },
62728
62729     // @private overrides the component method to set the correct dimensions to the chart.
62730     afterComponentLayout: function(width, height) {
62731         var me = this;
62732         if (Ext.isNumber(width) && Ext.isNumber(height)) {
62733             me.curWidth = width;
62734             me.curHeight = height;
62735             me.redraw(true);
62736         }
62737         this.callParent(arguments);
62738     },
62739
62740     /**
62741      * Redraws the chart. If animations are set this will animate the chart too. 
62742      * @param {Boolean} resize (optional) flag which changes the default origin points of the chart for animations.
62743      */
62744     redraw: function(resize) {
62745         var me = this,
62746             chartBBox = me.chartBBox = {
62747                 x: 0,
62748                 y: 0,
62749                 height: me.curHeight,
62750                 width: me.curWidth
62751             },
62752             legend = me.legend;
62753         me.surface.setSize(chartBBox.width, chartBBox.height);
62754         // Instantiate Series and Axes
62755         me.series.each(me.initializeSeries, me);
62756         me.axes.each(me.initializeAxis, me);
62757         //process all views (aggregated data etc) on stores
62758         //before rendering.
62759         me.axes.each(function(axis) {
62760             axis.processView();
62761         });
62762         me.axes.each(function(axis) {
62763             axis.drawAxis(true);
62764         });
62765
62766         // Create legend if not already created
62767         if (legend !== false) {
62768             legend.create();
62769         }
62770
62771         // Place axes properly, including influence from each other
62772         me.alignAxes();
62773
62774         // Reposition legend based on new axis alignment
62775         if (me.legend !== false) {
62776             legend.updatePosition();
62777         }
62778
62779         // Find the max gutter
62780         me.getMaxGutter();
62781
62782         // Draw axes and series
62783         me.resizing = !!resize;
62784
62785         me.axes.each(me.drawAxis, me);
62786         me.series.each(me.drawCharts, me);
62787         me.resizing = false;
62788     },
62789
62790     // @private set the store after rendering the chart.
62791     afterRender: function() {
62792         var ref,
62793             me = this;
62794         this.callParent();
62795
62796         if (me.categoryNames) {
62797             me.setCategoryNames(me.categoryNames);
62798         }
62799
62800         if (me.tipRenderer) {
62801             ref = me.getFunctionRef(me.tipRenderer);
62802             me.setTipRenderer(ref.fn, ref.scope);
62803         }
62804         me.bindStore(me.store, true);
62805         me.refresh();
62806     },
62807
62808     // @private get x and y position of the mouse cursor.
62809     getEventXY: function(e) {
62810         var me = this,
62811             box = this.surface.getRegion(),
62812             pageXY = e.getXY(),
62813             x = pageXY[0] - box.left,
62814             y = pageXY[1] - box.top;
62815         return [x, y];
62816     },
62817
62818     // @private wrap the mouse down position to delegate the event to the series.
62819     onClick: function(e) {
62820         var me = this,
62821             position = me.getEventXY(e),
62822             item;
62823
62824         // Ask each series if it has an item corresponding to (not necessarily exactly
62825         // on top of) the current mouse coords. Fire itemclick event.
62826         me.series.each(function(series) {
62827             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
62828                 if (series.getItemForPoint) {
62829                     item = series.getItemForPoint(position[0], position[1]);
62830                     if (item) {
62831                         series.fireEvent('itemclick', item);
62832                     }
62833                 }
62834             }
62835         }, me);
62836     },
62837
62838     // @private wrap the mouse down position to delegate the event to the series.
62839     onMouseDown: function(e) {
62840         var me = this,
62841             position = me.getEventXY(e),
62842             item;
62843
62844         if (me.mask) {
62845             me.mixins.mask.onMouseDown.call(me, e);
62846         }
62847         // Ask each series if it has an item corresponding to (not necessarily exactly
62848         // on top of) the current mouse coords. Fire mousedown event.
62849         me.series.each(function(series) {
62850             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
62851                 if (series.getItemForPoint) {
62852                     item = series.getItemForPoint(position[0], position[1]);
62853                     if (item) {
62854                         series.fireEvent('itemmousedown', item);
62855                     }
62856                 }
62857             }
62858         }, me);
62859     },
62860
62861     // @private wrap the mouse up event to delegate it to the series.
62862     onMouseUp: function(e) {
62863         var me = this,
62864             position = me.getEventXY(e),
62865             item;
62866
62867         if (me.mask) {
62868             me.mixins.mask.onMouseUp.call(me, e);
62869         }
62870         // Ask each series if it has an item corresponding to (not necessarily exactly
62871         // on top of) the current mouse coords. Fire mousedown event.
62872         me.series.each(function(series) {
62873             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
62874                 if (series.getItemForPoint) {
62875                     item = series.getItemForPoint(position[0], position[1]);
62876                     if (item) {
62877                         series.fireEvent('itemmouseup', item);
62878                     }
62879                 }
62880             }
62881         }, me);
62882     },
62883
62884     // @private wrap the mouse move event so it can be delegated to the series.
62885     onMouseMove: function(e) {
62886         var me = this,
62887             position = me.getEventXY(e),
62888             item, last, storeItem, storeField;
62889
62890         if (me.mask) {
62891             me.mixins.mask.onMouseMove.call(me, e);
62892         }
62893         // Ask each series if it has an item corresponding to (not necessarily exactly
62894         // on top of) the current mouse coords. Fire itemmouseover/out events.
62895         me.series.each(function(series) {
62896             if (Ext.draw.Draw.withinBox(position[0], position[1], series.bbox)) {
62897                 if (series.getItemForPoint) {
62898                     item = series.getItemForPoint(position[0], position[1]);
62899                     last = series._lastItemForPoint;
62900                     storeItem = series._lastStoreItem;
62901                     storeField = series._lastStoreField;
62902
62903
62904                     if (item !== last || item && (item.storeItem != storeItem || item.storeField != storeField)) {
62905                         if (last) {
62906                             series.fireEvent('itemmouseout', last);
62907                             delete series._lastItemForPoint;
62908                             delete series._lastStoreField;
62909                             delete series._lastStoreItem;
62910                         }
62911                         if (item) {
62912                             series.fireEvent('itemmouseover', item);
62913                             series._lastItemForPoint = item;
62914                             series._lastStoreItem = item.storeItem;
62915                             series._lastStoreField = item.storeField;
62916                         }
62917                     }
62918                 }
62919             } else {
62920                 last = series._lastItemForPoint;
62921                 if (last) {
62922                     series.fireEvent('itemmouseout', last);
62923                     delete series._lastItemForPoint;
62924                     delete series._lastStoreField;
62925                     delete series._lastStoreItem;
62926                 }
62927             }
62928         }, me);
62929     },
62930
62931     // @private handle mouse leave event.
62932     onMouseLeave: function(e) {
62933         var me = this;
62934         if (me.mask) {
62935             me.mixins.mask.onMouseLeave.call(me, e);
62936         }
62937         me.series.each(function(series) {
62938             delete series._lastItemForPoint;
62939         });
62940     },
62941
62942     // @private buffered refresh for when we update the store
62943     delayRefresh: function() {
62944         var me = this;
62945         if (!me.refreshTask) {
62946             me.refreshTask = Ext.create('Ext.util.DelayedTask', me.refresh, me);
62947         }
62948         me.refreshTask.delay(me.refreshBuffer);
62949     },
62950
62951     // @private
62952     refresh: function() {
62953         var me = this;
62954         if (me.rendered && me.curWidth !== undefined && me.curHeight !== undefined) {
62955             if (me.fireEvent('beforerefresh', me) !== false) {
62956                 me.redraw();
62957                 me.fireEvent('refresh', me);
62958             }
62959         }
62960     },
62961
62962     /**
62963      * Changes the data store bound to this chart and refreshes it.
62964      * @param {Ext.data.Store} store The store to bind to this chart
62965      */
62966     bindStore: function(store, initial) {
62967         var me = this;
62968         if (!initial && me.store) {
62969             if (store !== me.store && me.store.autoDestroy) {
62970                 me.store.destroyStore();
62971             }
62972             else {
62973                 me.store.un('datachanged', me.refresh, me);
62974                 me.store.un('add', me.delayRefresh, me);
62975                 me.store.un('remove', me.delayRefresh, me);
62976                 me.store.un('update', me.delayRefresh, me);
62977                 me.store.un('clear', me.refresh, me);
62978             }
62979         }
62980         if (store) {
62981             store = Ext.data.StoreManager.lookup(store);
62982             store.on({
62983                 scope: me,
62984                 datachanged: me.refresh,
62985                 add: me.delayRefresh,
62986                 remove: me.delayRefresh,
62987                 update: me.delayRefresh,
62988                 clear: me.refresh
62989             });
62990         }
62991         me.store = store;
62992         if (store && !initial) {
62993             me.refresh();
62994         }
62995     },
62996
62997     // @private Create Axis
62998     initializeAxis: function(axis) {
62999         var me = this,
63000             chartBBox = me.chartBBox,
63001             w = chartBBox.width,
63002             h = chartBBox.height,
63003             x = chartBBox.x,
63004             y = chartBBox.y,
63005             themeAttrs = me.themeAttrs,
63006             config = {
63007                 chart: me
63008             };
63009         if (themeAttrs) {
63010             config.axisStyle = Ext.apply({}, themeAttrs.axis);
63011             config.axisLabelLeftStyle = Ext.apply({}, themeAttrs.axisLabelLeft);
63012             config.axisLabelRightStyle = Ext.apply({}, themeAttrs.axisLabelRight);
63013             config.axisLabelTopStyle = Ext.apply({}, themeAttrs.axisLabelTop);
63014             config.axisLabelBottomStyle = Ext.apply({}, themeAttrs.axisLabelBottom);
63015             config.axisTitleLeftStyle = Ext.apply({}, themeAttrs.axisTitleLeft);
63016             config.axisTitleRightStyle = Ext.apply({}, themeAttrs.axisTitleRight);
63017             config.axisTitleTopStyle = Ext.apply({}, themeAttrs.axisTitleTop);
63018             config.axisTitleBottomStyle = Ext.apply({}, themeAttrs.axisTitleBottom);
63019         }
63020         switch (axis.position) {
63021             case 'top':
63022                 Ext.apply(config, {
63023                     length: w,
63024                     width: h,
63025                     x: x,
63026                     y: y
63027                 });
63028             break;
63029             case 'bottom':
63030                 Ext.apply(config, {
63031                     length: w,
63032                     width: h,
63033                     x: x,
63034                     y: h
63035                 });
63036             break;
63037             case 'left':
63038                 Ext.apply(config, {
63039                     length: h,
63040                     width: w,
63041                     x: x,
63042                     y: h
63043                 });
63044             break;
63045             case 'right':
63046                 Ext.apply(config, {
63047                     length: h,
63048                     width: w,
63049                     x: w,
63050                     y: h
63051                 });
63052             break;
63053         }
63054         if (!axis.chart) {
63055             Ext.apply(config, axis);
63056             axis = me.axes.replace(Ext.createByAlias('axis.' + axis.type.toLowerCase(), config));
63057         }
63058         else {
63059             Ext.apply(axis, config);
63060         }
63061     },
63062
63063
63064     /**
63065      * @private Adjust the dimensions and positions of each axis and the chart body area after accounting
63066      * for the space taken up on each side by the axes and legend.
63067      */
63068     alignAxes: function() {
63069         var me = this,
63070             axes = me.axes,
63071             legend = me.legend,
63072             edges = ['top', 'right', 'bottom', 'left'],
63073             chartBBox,
63074             insetPadding = me.insetPadding,
63075             insets = {
63076                 top: insetPadding,
63077                 right: insetPadding,
63078                 bottom: insetPadding,
63079                 left: insetPadding
63080             };
63081
63082         function getAxis(edge) {
63083             var i = axes.findIndex('position', edge);
63084             return (i < 0) ? null : axes.getAt(i);
63085         }
63086
63087         // Find the space needed by axes and legend as a positive inset from each edge
63088         Ext.each(edges, function(edge) {
63089             var isVertical = (edge === 'left' || edge === 'right'),
63090                 axis = getAxis(edge),
63091                 bbox;
63092
63093             // Add legend size if it's on this edge
63094             if (legend !== false) {
63095                 if (legend.position === edge) {
63096                     bbox = legend.getBBox();
63097                     insets[edge] += (isVertical ? bbox.width : bbox.height) + insets[edge];
63098                 }
63099             }
63100
63101             // Add axis size if there's one on this edge only if it has been
63102             //drawn before.
63103             if (axis && axis.bbox) {
63104                 bbox = axis.bbox;
63105                 insets[edge] += (isVertical ? bbox.width : bbox.height);
63106             }
63107         });
63108         // Build the chart bbox based on the collected inset values
63109         chartBBox = {
63110             x: insets.left,
63111             y: insets.top,
63112             width: me.curWidth - insets.left - insets.right,
63113             height: me.curHeight - insets.top - insets.bottom
63114         };
63115         me.chartBBox = chartBBox;
63116
63117         // Go back through each axis and set its length and position based on the
63118         // corresponding edge of the chartBBox
63119         axes.each(function(axis) {
63120             var pos = axis.position,
63121                 isVertical = (pos === 'left' || pos === 'right');
63122
63123             axis.x = (pos === 'right' ? chartBBox.x + chartBBox.width : chartBBox.x);
63124             axis.y = (pos === 'top' ? chartBBox.y : chartBBox.y + chartBBox.height);
63125             axis.width = (isVertical ? chartBBox.width : chartBBox.height);
63126             axis.length = (isVertical ? chartBBox.height : chartBBox.width);
63127         });
63128     },
63129
63130     // @private initialize the series.
63131     initializeSeries: function(series, idx) {
63132         var me = this,
63133             themeAttrs = me.themeAttrs,
63134             seriesObj, markerObj, seriesThemes, st,
63135             markerThemes, colorArrayStyle = [],
63136             i = 0, l,
63137             config = {
63138                 chart: me,
63139                 seriesId: series.seriesId
63140             };
63141         if (themeAttrs) {
63142             seriesThemes = themeAttrs.seriesThemes;
63143             markerThemes = themeAttrs.markerThemes;
63144             seriesObj = Ext.apply({}, themeAttrs.series);
63145             markerObj = Ext.apply({}, themeAttrs.marker);
63146             config.seriesStyle = Ext.apply(seriesObj, seriesThemes[idx % seriesThemes.length]);
63147             config.seriesLabelStyle = Ext.apply({}, themeAttrs.seriesLabel);
63148             config.markerStyle = Ext.apply(markerObj, markerThemes[idx % markerThemes.length]);
63149             if (themeAttrs.colors) {
63150                 config.colorArrayStyle = themeAttrs.colors;
63151             } else {
63152                 colorArrayStyle = [];
63153                 for (l = seriesThemes.length; i < l; i++) {
63154                     st = seriesThemes[i];
63155                     if (st.fill || st.stroke) {
63156                         colorArrayStyle.push(st.fill || st.stroke);
63157                     }
63158                 }
63159                 if (colorArrayStyle.length) {
63160                     config.colorArrayStyle = colorArrayStyle;
63161                 }
63162             }
63163             config.seriesIdx = idx;
63164         }
63165         if (series instanceof Ext.chart.series.Series) {
63166             Ext.apply(series, config);
63167         } else {
63168             Ext.applyIf(config, series);
63169             series = me.series.replace(Ext.createByAlias('series.' + series.type.toLowerCase(), config));
63170         }
63171         if (series.initialize) {
63172             series.initialize();
63173         }
63174     },
63175
63176     // @private
63177     getMaxGutter: function() {
63178         var me = this,
63179             maxGutter = [0, 0];
63180         me.series.each(function(s) {
63181             var gutter = s.getGutters && s.getGutters() || [0, 0];
63182             maxGutter[0] = Math.max(maxGutter[0], gutter[0]);
63183             maxGutter[1] = Math.max(maxGutter[1], gutter[1]);
63184         });
63185         me.maxGutter = maxGutter;
63186     },
63187
63188     // @private draw axis.
63189     drawAxis: function(axis) {
63190         axis.drawAxis();
63191     },
63192
63193     // @private draw series.
63194     drawCharts: function(series) {
63195         series.triggerafterrender = false;
63196         series.drawSeries();
63197         if (!this.animate) {
63198             series.fireEvent('afterrender');
63199         }
63200     },
63201
63202     // @private remove gently.
63203     destroy: function() {
63204         Ext.destroy(this.surface);
63205         this.bindStore(null);
63206         this.callParent(arguments);
63207     }
63208 });
63209
63210 /**
63211  * @class Ext.chart.Highlight
63212  * A mixin providing highlight functionality for Ext.chart.series.Series.
63213  */
63214 Ext.define('Ext.chart.Highlight', {
63215
63216     /* Begin Definitions */
63217
63218     requires: ['Ext.fx.Anim'],
63219
63220     /* End Definitions */
63221
63222     /**
63223      * Highlight the given series item.
63224      * @param {Boolean/Object} Default's false. Can also be an object width style properties (i.e fill, stroke, radius) 
63225      * or just use default styles per series by setting highlight = true.
63226      */
63227     highlight: false,
63228
63229     highlightCfg : null,
63230
63231     constructor: function(config) {
63232         if (config.highlight) {
63233             if (config.highlight !== true) { //is an object
63234                 this.highlightCfg = Ext.apply({}, config.highlight);
63235             }
63236             else {
63237                 this.highlightCfg = {
63238                     fill: '#fdd',
63239                     radius: 20,
63240                     lineWidth: 5,
63241                     stroke: '#f55'
63242                 };
63243             }
63244         }
63245     },
63246
63247     /**
63248      * Highlight the given series item.
63249      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
63250      */
63251     highlightItem: function(item) {
63252         if (!item) {
63253             return;
63254         }
63255         
63256         var me = this,
63257             sprite = item.sprite,
63258             opts = me.highlightCfg,
63259             surface = me.chart.surface,
63260             animate = me.chart.animate,
63261             p, from, to, pi;
63262
63263         if (!me.highlight || !sprite || sprite._highlighted) {
63264             return;
63265         }
63266         if (sprite._anim) {
63267             sprite._anim.paused = true;
63268         }
63269         sprite._highlighted = true;
63270         if (!sprite._defaults) {
63271             sprite._defaults = Ext.apply({}, sprite.attr);
63272             from = {};
63273             to = {};
63274             for (p in opts) {
63275                 if (! (p in sprite._defaults)) {
63276                     sprite._defaults[p] = surface.availableAttrs[p];
63277                 }
63278                 from[p] = sprite._defaults[p];
63279                 to[p] = opts[p];
63280                 if (Ext.isObject(opts[p])) {
63281                     from[p] = {};
63282                     to[p] = {};
63283                     Ext.apply(sprite._defaults[p], sprite.attr[p]);
63284                     Ext.apply(from[p], sprite._defaults[p]);
63285                     for (pi in sprite._defaults[p]) {
63286                         if (! (pi in opts[p])) {
63287                             to[p][pi] = from[p][pi];
63288                         } else {
63289                             to[p][pi] = opts[p][pi];
63290                         }
63291                     }
63292                     for (pi in opts[p]) {
63293                         if (! (pi in to[p])) {
63294                             to[p][pi] = opts[p][pi];
63295                         }
63296                     }
63297                 }
63298             }
63299             sprite._from = from;
63300             sprite._to = to;
63301             sprite._endStyle = to;
63302         }
63303         if (animate) {
63304             sprite._anim = Ext.create('Ext.fx.Anim', {
63305                 target: sprite,
63306                 from: sprite._from,
63307                 to: sprite._to,
63308                 duration: 150
63309             });
63310         } else {
63311             sprite.setAttributes(sprite._to, true);
63312         }
63313     },
63314
63315     /**
63316      * Un-highlight any existing highlights
63317      */
63318     unHighlightItem: function() {
63319         if (!this.highlight || !this.items) {
63320             return;
63321         }
63322
63323         var me = this,
63324             items = me.items,
63325             len = items.length,
63326             opts = me.highlightCfg,
63327             animate = me.chart.animate,
63328             i = 0,
63329             obj, p, sprite;
63330
63331         for (; i < len; i++) {
63332             if (!items[i]) {
63333                 continue;
63334             }
63335             sprite = items[i].sprite;
63336             if (sprite && sprite._highlighted) {
63337                 if (sprite._anim) {
63338                     sprite._anim.paused = true;
63339                 }
63340                 obj = {};
63341                 for (p in opts) {
63342                     if (Ext.isObject(sprite._defaults[p])) {
63343                         obj[p] = {};
63344                         Ext.apply(obj[p], sprite._defaults[p]);
63345                     }
63346                     else {
63347                         obj[p] = sprite._defaults[p];
63348                     }
63349                 }
63350                 if (animate) {
63351                     //sprite._to = obj;
63352                     sprite._endStyle = obj;
63353                     sprite._anim = Ext.create('Ext.fx.Anim', {
63354                         target: sprite,
63355                         to: obj,
63356                         duration: 150
63357                     });
63358                 }
63359                 else {
63360                     sprite.setAttributes(obj, true);
63361                 }
63362                 delete sprite._highlighted;
63363                 //delete sprite._defaults;
63364             }
63365         }
63366     },
63367
63368     cleanHighlights: function() {
63369         if (!this.highlight) {
63370             return;
63371         }
63372
63373         var group = this.group,
63374             markerGroup = this.markerGroup,
63375             i = 0,
63376             l;
63377         for (l = group.getCount(); i < l; i++) {
63378             delete group.getAt(i)._defaults;
63379         }
63380         if (markerGroup) {
63381             for (l = markerGroup.getCount(); i < l; i++) {
63382                 delete markerGroup.getAt(i)._defaults;
63383             }
63384         }
63385     }
63386 });
63387 /**
63388  * @class Ext.chart.Label
63389  *
63390  * Labels is a mixin to the Series class. Labels methods are implemented
63391  * in each of the Series (Pie, Bar, etc) for label creation and placement.
63392  *
63393  * The methods implemented by the Series are:
63394  *
63395  * - **`onCreateLabel(storeItem, item, i, display)`** Called each time a new label is created.
63396  *   The arguments of the method are:
63397  *   - *`storeItem`* The element of the store that is related to the label sprite.
63398  *   - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
63399  *     used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
63400  *   - *`i`* The index of the element created (i.e the first created label, second created label, etc)
63401  *   - *`display`* The display type. May be <b>false</b> if the label is hidden
63402  *
63403  *  - **`onPlaceLabel(label, storeItem, item, i, display, animate)`** Called for updating the position of the label.
63404  *    The arguments of the method are:
63405  *    - *`label`* The sprite label.</li>
63406  *    - *`storeItem`* The element of the store that is related to the label sprite</li>
63407  *    - *`item`* The item related to the label sprite. An item is an object containing the position of the shape
63408  *      used to describe the visualization and also pointing to the actual shape (circle, rectangle, path, etc).
63409  *    - *`i`* The index of the element to be updated (i.e. whether it is the first, second, third from the labelGroup)
63410  *    - *`display`* The display type. May be <b>false</b> if the label is hidden.
63411  *    - *`animate`* A boolean value to set or unset animations for the labels.
63412  */
63413 Ext.define('Ext.chart.Label', {
63414
63415     /* Begin Definitions */
63416
63417     requires: ['Ext.draw.Color'],
63418
63419     /* End Definitions */
63420
63421     /**
63422      * @cfg {Object} label
63423      * Object with the following properties:
63424      *
63425      * - **display** : String
63426      *
63427      *   Specifies the presence and position of labels for each pie slice. Either "rotate", "middle", "insideStart",
63428      *   "insideEnd", "outside", "over", "under", or "none" to prevent label rendering.
63429      *   Default value: 'none'.
63430      *
63431      * - **color** : String
63432      *
63433      *   The color of the label text.
63434      *   Default value: '#000' (black).
63435      *
63436      * - **contrast** : Boolean
63437      *
63438      *   True to render the label in contrasting color with the backround.
63439      *   Default value: false.
63440      *
63441      * - **field** : String
63442      *
63443      *   The name of the field to be displayed in the label.
63444      *   Default value: 'name'.
63445      *
63446      * - **minMargin** : Number
63447      *
63448      *   Specifies the minimum distance from a label to the origin of the visualization.
63449      *   This parameter is useful when using PieSeries width variable pie slice lengths.
63450      *   Default value: 50.
63451      *
63452      * - **font** : String
63453      *
63454      *   The font used for the labels.
63455      *   Default value: "11px Helvetica, sans-serif".
63456      *
63457      * - **orientation** : String
63458      *
63459      *   Either "horizontal" or "vertical".
63460      *   Dafault value: "horizontal".
63461      *
63462      * - **renderer** : Function
63463      *
63464      *   Optional function for formatting the label into a displayable value.
63465      *   Default value: function(v) { return v; }
63466      */
63467
63468     //@private a regex to parse url type colors.
63469     colorStringRe: /url\s*\(\s*#([^\/)]+)\s*\)/,
63470
63471     //@private the mixin constructor. Used internally by Series.
63472     constructor: function(config) {
63473         var me = this;
63474         me.label = Ext.applyIf(me.label || {},
63475         {
63476             display: "none",
63477             color: "#000",
63478             field: "name",
63479             minMargin: 50,
63480             font: "11px Helvetica, sans-serif",
63481             orientation: "horizontal",
63482             renderer: function(v) {
63483                 return v;
63484             }
63485         });
63486
63487         if (me.label.display !== 'none') {
63488             me.labelsGroup = me.chart.surface.getGroup(me.seriesId + '-labels');
63489         }
63490     },
63491
63492     //@private a method to render all labels in the labelGroup
63493     renderLabels: function() {
63494         var me = this,
63495             chart = me.chart,
63496             gradients = chart.gradients,
63497             items = me.items,
63498             animate = chart.animate,
63499             config = me.label,
63500             display = config.display,
63501             color = config.color,
63502             field = [].concat(config.field),
63503             group = me.labelsGroup,
63504             groupLength = (group || 0) && group.length,
63505             store = me.chart.store,
63506             len = store.getCount(),
63507             itemLength = (items || 0) && items.length,
63508             ratio = itemLength / len,
63509             gradientsCount = (gradients || 0) && gradients.length,
63510             Color = Ext.draw.Color,
63511             hides = [],
63512             gradient, i, count, groupIndex, index, j, k, colorStopTotal, colorStopIndex, colorStop, item, label,
63513             storeItem, sprite, spriteColor, spriteBrightness, labelColor, colorString;
63514
63515         if (display == 'none') {
63516             return;
63517         }
63518         // no items displayed, hide all labels
63519         if(itemLength == 0){
63520             while(groupLength--)
63521                 hides.push(groupLength);
63522         }else{
63523             for (i = 0, count = 0, groupIndex = 0; i < len; i++) {
63524                 index = 0;
63525                 for (j = 0; j < ratio; j++) {
63526                     item = items[count];
63527                     label = group.getAt(groupIndex);
63528                     storeItem = store.getAt(i);
63529                     //check the excludes
63530                     while(this.__excludes && this.__excludes[index] && ratio > 1) {
63531                         if(field[j]){
63532                             hides.push(groupIndex);
63533                         }
63534                         index++;
63535
63536                     }
63537
63538                     if (!item && label) {
63539                         label.hide(true);
63540                         groupIndex++;
63541                     }
63542
63543                     if (item && field[j]) {
63544                         if (!label) {
63545                             label = me.onCreateLabel(storeItem, item, i, display, j, index);
63546                         }
63547                         me.onPlaceLabel(label, storeItem, item, i, display, animate, j, index);
63548                         groupIndex++;
63549
63550                         //set contrast
63551                         if (config.contrast && item.sprite) {
63552                             sprite = item.sprite;
63553                             //set the color string to the color to be set.
63554                             if (sprite._endStyle) {
63555                                 colorString = sprite._endStyle.fill;
63556                             }
63557                             else if (sprite._to) {
63558                                 colorString = sprite._to.fill;
63559                             }
63560                             else {
63561                                 colorString = sprite.attr.fill;
63562                             }
63563                             colorString = colorString || sprite.attr.fill;
63564
63565                             spriteColor = Color.fromString(colorString);
63566                             //color wasn't parsed property maybe because it's a gradient id
63567                             if (colorString && !spriteColor) {
63568                                 colorString = colorString.match(me.colorStringRe)[1];
63569                                 for (k = 0; k < gradientsCount; k++) {
63570                                     gradient = gradients[k];
63571                                     if (gradient.id == colorString) {
63572                                         //avg color stops
63573                                         colorStop = 0; colorStopTotal = 0;
63574                                         for (colorStopIndex in gradient.stops) {
63575                                             colorStop++;
63576                                             colorStopTotal += Color.fromString(gradient.stops[colorStopIndex].color).getGrayscale();
63577                                         }
63578                                         spriteBrightness = (colorStopTotal / colorStop) / 255;
63579                                         break;
63580                                     }
63581                                 }
63582                             }
63583                             else {
63584                                 spriteBrightness = spriteColor.getGrayscale() / 255;
63585                             }
63586                             if (label.isOutside) {
63587                                 spriteBrightness = 1;
63588                             }
63589                             labelColor = Color.fromString(label.attr.color || label.attr.fill).getHSL();
63590                             labelColor[2] = spriteBrightness > 0.5 ? 0.2 : 0.8;
63591                             label.setAttributes({
63592                                 fill: String(Color.fromHSL.apply({}, labelColor))
63593                             }, true);
63594                         }
63595
63596                     }
63597                     count++;
63598                     index++;
63599                 }
63600             }
63601         }
63602         me.hideLabels(hides);
63603     },
63604     hideLabels: function(hides){
63605         var labelsGroup = this.labelsGroup,
63606             hlen = hides.length;
63607         while(hlen--)
63608             labelsGroup.getAt(hides[hlen]).hide(true);
63609     }
63610 });
63611 Ext.define('Ext.chart.MaskLayer', {
63612     extend: 'Ext.Component',
63613     
63614     constructor: function(config) {
63615         config = Ext.apply(config || {}, {
63616             style: 'position:absolute;background-color:#888;cursor:move;opacity:0.6;border:1px solid #222;'
63617         });
63618         this.callParent([config]);    
63619     },
63620     
63621     initComponent: function() {
63622         var me = this;
63623         me.callParent(arguments);
63624         me.addEvents(
63625             'mousedown',
63626             'mouseup',
63627             'mousemove',
63628             'mouseenter',
63629             'mouseleave'
63630         );
63631     },
63632
63633     initDraggable: function() {
63634         this.callParent(arguments);
63635         this.dd.onStart = function (e) {
63636             var me = this,
63637                 comp = me.comp;
63638     
63639             // Cache the start [X, Y] array
63640             this.startPosition = comp.getPosition(true);
63641     
63642             // If client Component has a ghost method to show a lightweight version of itself
63643             // then use that as a drag proxy unless configured to liveDrag.
63644             if (comp.ghost && !comp.liveDrag) {
63645                  me.proxy = comp.ghost();
63646                  me.dragTarget = me.proxy.header.el;
63647             }
63648     
63649             // Set the constrainTo Region before we start dragging.
63650             if (me.constrain || me.constrainDelegate) {
63651                 me.constrainTo = me.calculateConstrainRegion();
63652             }
63653         };
63654     }
63655 });
63656 /**
63657  * @class Ext.chart.TipSurface
63658  * @ignore
63659  */
63660 Ext.define('Ext.chart.TipSurface', {
63661
63662     /* Begin Definitions */
63663
63664     extend: 'Ext.draw.Component',
63665
63666     /* End Definitions */
63667
63668     spriteArray: false,
63669     renderFirst: true,
63670
63671     constructor: function(config) {
63672         this.callParent([config]);
63673         if (config.sprites) {
63674             this.spriteArray = [].concat(config.sprites);
63675             delete config.sprites;
63676         }
63677     },
63678
63679     onRender: function() {
63680         var me = this,
63681             i = 0,
63682             l = 0,
63683             sp,
63684             sprites;
63685             this.callParent(arguments);
63686         sprites = me.spriteArray;
63687         if (me.renderFirst && sprites) {
63688             me.renderFirst = false;
63689             for (l = sprites.length; i < l; i++) {
63690                 sp = me.surface.add(sprites[i]);
63691                 sp.setAttributes({
63692                     hidden: false
63693                 },
63694                 true);
63695             }
63696         }
63697     }
63698 });
63699
63700 /**
63701  * @class Ext.chart.Tip
63702  * Provides tips for Ext.chart.series.Series.
63703  */
63704 Ext.define('Ext.chart.Tip', {
63705
63706     /* Begin Definitions */
63707
63708     requires: ['Ext.tip.ToolTip', 'Ext.chart.TipSurface'],
63709
63710     /* End Definitions */
63711
63712     constructor: function(config) {
63713         var me = this,
63714             surface,
63715             sprites,
63716             tipSurface;
63717         if (config.tips) {
63718             me.tipTimeout = null;
63719             me.tipConfig = Ext.apply({}, config.tips, {
63720                 renderer: Ext.emptyFn,
63721                 constrainPosition: false
63722             });
63723             me.tooltip = Ext.create('Ext.tip.ToolTip', me.tipConfig);
63724             me.chart.surface.on('mousemove', me.tooltip.onMouseMove, me.tooltip);
63725             me.chart.surface.on('mouseleave', function() {
63726                 me.hideTip();
63727             });
63728             if (me.tipConfig.surface) {
63729                 //initialize a surface
63730                 surface = me.tipConfig.surface;
63731                 sprites = surface.sprites;
63732                 tipSurface = Ext.create('Ext.chart.TipSurface', {
63733                     id: 'tipSurfaceComponent',
63734                     sprites: sprites
63735                 });
63736                 if (surface.width && surface.height) {
63737                     tipSurface.setSize(surface.width, surface.height);
63738                 }
63739                 me.tooltip.add(tipSurface);
63740                 me.spriteTip = tipSurface;
63741             }
63742         }
63743     },
63744
63745     showTip: function(item) {
63746         var me = this;
63747         if (!me.tooltip) {
63748             return;
63749         }
63750         clearTimeout(me.tipTimeout);
63751         var tooltip = me.tooltip,
63752             spriteTip = me.spriteTip,
63753             tipConfig = me.tipConfig,
63754             trackMouse = tooltip.trackMouse,
63755             sprite, surface, surfaceExt, pos, x, y;
63756         if (!trackMouse) {
63757             tooltip.trackMouse = true;
63758             sprite = item.sprite;
63759             surface = sprite.surface;
63760             surfaceExt = Ext.get(surface.getId());
63761             if (surfaceExt) {
63762                 pos = surfaceExt.getXY();
63763                 x = pos[0] + (sprite.attr.x || 0) + (sprite.attr.translation && sprite.attr.translation.x || 0);
63764                 y = pos[1] + (sprite.attr.y || 0) + (sprite.attr.translation && sprite.attr.translation.y || 0);
63765                 tooltip.targetXY = [x, y];
63766             }
63767         }
63768         if (spriteTip) {
63769             tipConfig.renderer.call(tooltip, item.storeItem, item, spriteTip.surface);
63770         } else {
63771             tipConfig.renderer.call(tooltip, item.storeItem, item);
63772         }
63773         tooltip.show();
63774         tooltip.trackMouse = trackMouse;
63775     },
63776
63777     hideTip: function(item) {
63778         var tooltip = this.tooltip;
63779         if (!tooltip) {
63780             return;
63781         }
63782         clearTimeout(this.tipTimeout);
63783         this.tipTimeout = setTimeout(function() {
63784             tooltip.hide();
63785         }, 0);
63786     }
63787 });
63788 /**
63789  * @class Ext.chart.axis.Abstract
63790  * Base class for all axis classes.
63791  * @private
63792  */
63793 Ext.define('Ext.chart.axis.Abstract', {
63794
63795     /* Begin Definitions */
63796
63797     requires: ['Ext.chart.Chart'],
63798
63799     /* End Definitions */
63800
63801     /**
63802      * Creates new Axis.
63803      * @param {Object} config (optional) Config options.
63804      */
63805     constructor: function(config) {
63806         config = config || {};
63807
63808         var me = this,
63809             pos = config.position || 'left';
63810
63811         pos = pos.charAt(0).toUpperCase() + pos.substring(1);
63812         //axisLabel(Top|Bottom|Right|Left)Style
63813         config.label = Ext.apply(config['axisLabel' + pos + 'Style'] || {}, config.label || {});
63814         config.axisTitleStyle = Ext.apply(config['axisTitle' + pos + 'Style'] || {}, config.labelTitle || {});
63815         Ext.apply(me, config);
63816         me.fields = [].concat(me.fields);
63817         this.callParent();
63818         me.labels = [];
63819         me.getId();
63820         me.labelGroup = me.chart.surface.getGroup(me.axisId + "-labels");
63821     },
63822
63823     alignment: null,
63824     grid: false,
63825     steps: 10,
63826     x: 0,
63827     y: 0,
63828     minValue: 0,
63829     maxValue: 0,
63830
63831     getId: function() {
63832         return this.axisId || (this.axisId = Ext.id(null, 'ext-axis-'));
63833     },
63834
63835     /*
63836       Called to process a view i.e to make aggregation and filtering over
63837       a store creating a substore to be used to render the axis. Since many axes
63838       may do different things on the data and we want the final result of all these
63839       operations to be rendered we need to call processView on all axes before drawing
63840       them.
63841     */
63842     processView: Ext.emptyFn,
63843
63844     drawAxis: Ext.emptyFn,
63845     addDisplayAndLabels: Ext.emptyFn
63846 });
63847
63848 /**
63849  * @class Ext.chart.axis.Axis
63850  * @extends Ext.chart.axis.Abstract
63851  *
63852  * Defines axis for charts. The axis position, type, style can be configured.
63853  * The axes are defined in an axes array of configuration objects where the type,
63854  * field, grid and other configuration options can be set. To know more about how
63855  * to create a Chart please check the Chart class documentation. Here's an example for the axes part:
63856  * An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
63857  *
63858  *     axes: [{
63859  *         type: 'Numeric',
63860  *         grid: true,
63861  *         position: 'left',
63862  *         fields: ['data1', 'data2', 'data3'],
63863  *         title: 'Number of Hits',
63864  *         grid: {
63865  *             odd: {
63866  *                 opacity: 1,
63867  *                 fill: '#ddd',
63868  *                 stroke: '#bbb',
63869  *                 'stroke-width': 1
63870  *             }
63871  *         },
63872  *         minimum: 0
63873  *     }, {
63874  *         type: 'Category',
63875  *         position: 'bottom',
63876  *         fields: ['name'],
63877  *         title: 'Month of the Year',
63878  *         grid: true,
63879  *         label: {
63880  *             rotate: {
63881  *                 degrees: 315
63882  *             }
63883  *         }
63884  *     }]
63885  *
63886  * 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
63887  * 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.
63888  * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the
63889  * category axis the labels will be rotated so they can fit the space better.
63890  */
63891 Ext.define('Ext.chart.axis.Axis', {
63892
63893     /* Begin Definitions */
63894
63895     extend: 'Ext.chart.axis.Abstract',
63896
63897     alternateClassName: 'Ext.chart.Axis',
63898
63899     requires: ['Ext.draw.Draw'],
63900
63901     /* End Definitions */
63902
63903     /**
63904      * @cfg {Boolean/Object} grid
63905      * The grid configuration enables you to set a background grid for an axis.
63906      * If set to *true* on a vertical axis, vertical lines will be drawn.
63907      * If set to *true* on a horizontal axis, horizontal lines will be drawn.
63908      * If both are set, a proper grid with horizontal and vertical lines will be drawn.
63909      *
63910      * You can set specific options for the grid configuration for odd and/or even lines/rows.
63911      * Since the rows being drawn are rectangle sprites, you can set to an odd or even property
63912      * all styles that apply to {@link Ext.draw.Sprite}. For more information on all the style
63913      * properties you can set please take a look at {@link Ext.draw.Sprite}. Some useful style properties are `opacity`, `fill`, `stroke`, `stroke-width`, etc.
63914      *
63915      * The possible values for a grid option are then *true*, *false*, or an object with `{ odd, even }` properties
63916      * where each property contains a sprite style descriptor object that is defined in {@link Ext.draw.Sprite}.
63917      *
63918      * For example:
63919      *
63920      *     axes: [{
63921      *         type: 'Numeric',
63922      *         grid: true,
63923      *         position: 'left',
63924      *         fields: ['data1', 'data2', 'data3'],
63925      *         title: 'Number of Hits',
63926      *         grid: {
63927      *             odd: {
63928      *                 opacity: 1,
63929      *                 fill: '#ddd',
63930      *                 stroke: '#bbb',
63931      *                 'stroke-width': 1
63932      *             }
63933      *         }
63934      *     }, {
63935      *         type: 'Category',
63936      *         position: 'bottom',
63937      *         fields: ['name'],
63938      *         title: 'Month of the Year',
63939      *         grid: true
63940      *     }]
63941      *
63942      */
63943
63944     /**
63945      * @cfg {Number} majorTickSteps
63946      * If `minimum` and `maximum` are specified it forces the number of major ticks to the specified value.
63947      */
63948
63949     /**
63950      * @cfg {Number} minorTickSteps
63951      * The number of small ticks between two major ticks. Default is zero.
63952      */
63953
63954     /**
63955      * @cfg {String} title
63956      * The title for the Axis
63957      */
63958
63959     //@private force min/max values from store
63960     forceMinMax: false,
63961
63962     /**
63963      * @cfg {Number} dashSize
63964      * The size of the dash marker. Default's 3.
63965      */
63966     dashSize: 3,
63967
63968     /**
63969      * @cfg {String} position
63970      * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`. Default's `bottom`.
63971      */
63972     position: 'bottom',
63973
63974     // @private
63975     skipFirst: false,
63976
63977     /**
63978      * @cfg {Number} length
63979      * Offset axis position. Default's 0.
63980      */
63981     length: 0,
63982
63983     /**
63984      * @cfg {Number} width
63985      * Offset axis width. Default's 0.
63986      */
63987     width: 0,
63988
63989     majorTickSteps: false,
63990
63991     // @private
63992     applyData: Ext.emptyFn,
63993
63994     getRange: function () {
63995         var me = this,
63996             store = me.chart.getChartStore(),
63997             fields = me.fields,
63998             ln = fields.length,
63999             math = Math,
64000             mmax = math.max,
64001             mmin = math.min,
64002             aggregate = false,
64003             min = isNaN(me.minimum) ? Infinity : me.minimum,
64004             max = isNaN(me.maximum) ? -Infinity : me.maximum,
64005             total = 0, i, l, value, values, rec,
64006             excludes = [],
64007             series = me.chart.series.items;
64008
64009         //if one series is stacked I have to aggregate the values
64010         //for the scale.
64011         // TODO(zhangbei): the code below does not support series that stack on 1 side but non-stacked axis
64012         // listed in axis config. For example, a Area series whose axis : ['left', 'bottom'].
64013         // Assuming only stack on y-axis.
64014         // CHANGED BY Nicolas: I removed the check `me.position == 'left'` and `me.position == 'right'` since 
64015         // it was constraining the minmax calculation to y-axis stacked
64016         // visualizations.
64017         for (i = 0, l = series.length; !aggregate && i < l; i++) {
64018             aggregate = aggregate || series[i].stacked;
64019             excludes = series[i].__excludes || excludes;
64020         }
64021         store.each(function(record) {
64022             if (aggregate) {
64023                 if (!isFinite(min)) {
64024                     min = 0;
64025                 }
64026                 for (values = [0, 0], i = 0; i < ln; i++) {
64027                     if (excludes[i]) {
64028                         continue;
64029                     }
64030                     rec = record.get(fields[i]);
64031                     values[+(rec > 0)] += math.abs(rec);
64032                 }
64033                 max = mmax(max, -values[0], +values[1]);
64034                 min = mmin(min, -values[0], +values[1]);
64035             }
64036             else {
64037                 for (i = 0; i < ln; i++) {
64038                     if (excludes[i]) {
64039                         continue;
64040                     }
64041                     value = record.get(fields[i]);
64042                     max = mmax(max, +value);
64043                     min = mmin(min, +value);
64044                 }
64045             }
64046         });
64047         if (!isFinite(max)) {
64048             max = me.prevMax || 0;
64049         }
64050         if (!isFinite(min)) {
64051             min = me.prevMin || 0;
64052         }
64053         //normalize min max for snapEnds.
64054         if (min != max && (max != Math.floor(max))) {
64055             max = Math.floor(max) + 1;
64056         }
64057
64058         if (!isNaN(me.minimum)) {
64059             min = me.minimum;
64060         }
64061         
64062         if (!isNaN(me.maximum)) {
64063             max = me.maximum;
64064         }
64065
64066         return {min: min, max: max};
64067     },
64068
64069     // @private creates a structure with start, end and step points.
64070     calcEnds: function() {
64071         var me = this,
64072             fields = me.fields,
64073             range = me.getRange(),
64074             min = range.min,
64075             max = range.max,
64076             outfrom, outto, out;
64077
64078         out = Ext.draw.Draw.snapEnds(min, max, me.majorTickSteps !== false ?  (me.majorTickSteps +1) : me.steps);
64079         outfrom = out.from;
64080         outto = out.to;
64081         if (me.forceMinMax) {
64082             if (!isNaN(max)) {
64083                 out.to = max;
64084             }
64085             if (!isNaN(min)) {
64086                 out.from = min;
64087             }
64088         }
64089         if (!isNaN(me.maximum)) {
64090             //TODO(nico) users are responsible for their own minimum/maximum values set.
64091             //Clipping should be added to remove lines in the chart which are below the axis.
64092             out.to = me.maximum;
64093         }
64094         if (!isNaN(me.minimum)) {
64095             //TODO(nico) users are responsible for their own minimum/maximum values set.
64096             //Clipping should be added to remove lines in the chart which are below the axis.
64097             out.from = me.minimum;
64098         }
64099
64100         //Adjust after adjusting minimum and maximum
64101         out.step = (out.to - out.from) / (outto - outfrom) * out.step;
64102
64103         if (me.adjustMaximumByMajorUnit) {
64104             out.to += out.step;
64105         }
64106         if (me.adjustMinimumByMajorUnit) {
64107             out.from -= out.step;
64108         }
64109         me.prevMin = min == max? 0 : min;
64110         me.prevMax = max;
64111         return out;
64112     },
64113
64114     /**
64115      * Renders the axis into the screen and updates its position.
64116      */
64117     drawAxis: function (init) {
64118         var me = this,
64119             i, j,
64120             x = me.x,
64121             y = me.y,
64122             gutterX = me.chart.maxGutter[0],
64123             gutterY = me.chart.maxGutter[1],
64124             dashSize = me.dashSize,
64125             subDashesX = me.minorTickSteps || 0,
64126             subDashesY = me.minorTickSteps || 0,
64127             length = me.length,
64128             position = me.position,
64129             inflections = [],
64130             calcLabels = false,
64131             stepCalcs = me.applyData(),
64132             step = stepCalcs.step,
64133             steps = stepCalcs.steps,
64134             from = stepCalcs.from,
64135             to = stepCalcs.to,
64136             trueLength,
64137             currentX,
64138             currentY,
64139             path,
64140             prev,
64141             dashesX,
64142             dashesY,
64143             delta;
64144
64145         //If no steps are specified
64146         //then don't draw the axis. This generally happens
64147         //when an empty store.
64148         if (me.hidden || isNaN(step) || (from == to)) {
64149             return;
64150         }
64151
64152         me.from = stepCalcs.from;
64153         me.to = stepCalcs.to;
64154         if (position == 'left' || position == 'right') {
64155             currentX = Math.floor(x) + 0.5;
64156             path = ["M", currentX, y, "l", 0, -length];
64157             trueLength = length - (gutterY * 2);
64158         }
64159         else {
64160             currentY = Math.floor(y) + 0.5;
64161             path = ["M", x, currentY, "l", length, 0];
64162             trueLength = length - (gutterX * 2);
64163         }
64164
64165         delta = trueLength / (steps || 1);
64166         dashesX = Math.max(subDashesX +1, 0);
64167         dashesY = Math.max(subDashesY +1, 0);
64168         if (me.type == 'Numeric' || me.type == 'Time') {
64169             calcLabels = true;
64170             me.labels = [stepCalcs.from];
64171         }
64172         if (position == 'right' || position == 'left') {
64173             currentY = y - gutterY;
64174             currentX = x - ((position == 'left') * dashSize * 2);
64175             while (currentY >= y - gutterY - trueLength) {
64176                 path.push("M", currentX, Math.floor(currentY) + 0.5, "l", dashSize * 2 + 1, 0);
64177                 if (currentY != y - gutterY) {
64178                     for (i = 1; i < dashesY; i++) {
64179                         path.push("M", currentX + dashSize, Math.floor(currentY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
64180                     }
64181                 }
64182                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
64183                 currentY -= delta;
64184                 if (calcLabels) {
64185                     me.labels.push(me.labels[me.labels.length -1] + step);
64186                 }
64187                 if (delta === 0) {
64188                     break;
64189                 }
64190             }
64191             if (Math.round(currentY + delta - (y - gutterY - trueLength))) {
64192                 path.push("M", currentX, Math.floor(y - length + gutterY) + 0.5, "l", dashSize * 2 + 1, 0);
64193                 for (i = 1; i < dashesY; i++) {
64194                     path.push("M", currentX + dashSize, Math.floor(y - length + gutterY + delta * i / dashesY) + 0.5, "l", dashSize + 1, 0);
64195                 }
64196                 inflections.push([ Math.floor(x), Math.floor(currentY) ]);
64197                 if (calcLabels) {
64198                     me.labels.push(me.labels[me.labels.length -1] + step);
64199                 }
64200             }
64201         } else {
64202             currentX = x + gutterX;
64203             currentY = y - ((position == 'top') * dashSize * 2);
64204             while (currentX <= x + gutterX + trueLength) {
64205                 path.push("M", Math.floor(currentX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
64206                 if (currentX != x + gutterX) {
64207                     for (i = 1; i < dashesX; i++) {
64208                         path.push("M", Math.floor(currentX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
64209                     }
64210                 }
64211                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
64212                 currentX += delta;
64213                 if (calcLabels) {
64214                     me.labels.push(me.labels[me.labels.length -1] + step);
64215                 }
64216                 if (delta === 0) {
64217                     break;
64218                 }
64219             }
64220             if (Math.round(currentX - delta - (x + gutterX + trueLength))) {
64221                 path.push("M", Math.floor(x + length - gutterX) + 0.5, currentY, "l", 0, dashSize * 2 + 1);
64222                 for (i = 1; i < dashesX; i++) {
64223                     path.push("M", Math.floor(x + length - gutterX - delta * i / dashesX) + 0.5, currentY, "l", 0, dashSize + 1);
64224                 }
64225                 inflections.push([ Math.floor(currentX), Math.floor(y) ]);
64226                 if (calcLabels) {
64227                     me.labels.push(me.labels[me.labels.length -1] + step);
64228                 }
64229             }
64230         }
64231         if (!me.axis) {
64232             me.axis = me.chart.surface.add(Ext.apply({
64233                 type: 'path',
64234                 path: path
64235             }, me.axisStyle));
64236         }
64237         me.axis.setAttributes({
64238             path: path
64239         }, true);
64240         me.inflections = inflections;
64241         if (!init && me.grid) {
64242             me.drawGrid();
64243         }
64244         me.axisBBox = me.axis.getBBox();
64245         me.drawLabel();
64246     },
64247
64248     /**
64249      * Renders an horizontal and/or vertical grid into the Surface.
64250      */
64251     drawGrid: function() {
64252         var me = this,
64253             surface = me.chart.surface,
64254             grid = me.grid,
64255             odd = grid.odd,
64256             even = grid.even,
64257             inflections = me.inflections,
64258             ln = inflections.length - ((odd || even)? 0 : 1),
64259             position = me.position,
64260             gutter = me.chart.maxGutter,
64261             width = me.width - 2,
64262             vert = false,
64263             point, prevPoint,
64264             i = 1,
64265             path = [], styles, lineWidth, dlineWidth,
64266             oddPath = [], evenPath = [];
64267
64268         if ((gutter[1] !== 0 && (position == 'left' || position == 'right')) ||
64269             (gutter[0] !== 0 && (position == 'top' || position == 'bottom'))) {
64270             i = 0;
64271             ln++;
64272         }
64273         for (; i < ln; i++) {
64274             point = inflections[i];
64275             prevPoint = inflections[i - 1];
64276             if (odd || even) {
64277                 path = (i % 2)? oddPath : evenPath;
64278                 styles = ((i % 2)? odd : even) || {};
64279                 lineWidth = (styles.lineWidth || styles['stroke-width'] || 0) / 2;
64280                 dlineWidth = 2 * lineWidth;
64281                 if (position == 'left') {
64282                     path.push("M", prevPoint[0] + 1 + lineWidth, prevPoint[1] + 0.5 - lineWidth,
64283                               "L", prevPoint[0] + 1 + width - lineWidth, prevPoint[1] + 0.5 - lineWidth,
64284                               "L", point[0] + 1 + width - lineWidth, point[1] + 0.5 + lineWidth,
64285                               "L", point[0] + 1 + lineWidth, point[1] + 0.5 + lineWidth, "Z");
64286                 }
64287                 else if (position == 'right') {
64288                     path.push("M", prevPoint[0] - lineWidth, prevPoint[1] + 0.5 - lineWidth,
64289                               "L", prevPoint[0] - width + lineWidth, prevPoint[1] + 0.5 - lineWidth,
64290                               "L", point[0] - width + lineWidth, point[1] + 0.5 + lineWidth,
64291                               "L", point[0] - lineWidth, point[1] + 0.5 + lineWidth, "Z");
64292                 }
64293                 else if (position == 'top') {
64294                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + lineWidth,
64295                               "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] + 1 + width - lineWidth,
64296                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + width - lineWidth,
64297                               "L", point[0] + 0.5 - lineWidth, point[1] + 1 + lineWidth, "Z");
64298                 }
64299                 else {
64300                     path.push("M", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - lineWidth,
64301                             "L", prevPoint[0] + 0.5 + lineWidth, prevPoint[1] - width + lineWidth,
64302                             "L", point[0] + 0.5 - lineWidth, point[1] - width + lineWidth,
64303                             "L", point[0] + 0.5 - lineWidth, point[1] - lineWidth, "Z");
64304                 }
64305             } else {
64306                 if (position == 'left') {
64307                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", width, 0]);
64308                 }
64309                 else if (position == 'right') {
64310                     path = path.concat(["M", point[0] - 0.5, point[1] + 0.5, "l", -width, 0]);
64311                 }
64312                 else if (position == 'top') {
64313                     path = path.concat(["M", point[0] + 0.5, point[1] + 0.5, "l", 0, width]);
64314                 }
64315                 else {
64316                     path = path.concat(["M", point[0] + 0.5, point[1] - 0.5, "l", 0, -width]);
64317                 }
64318             }
64319         }
64320         if (odd || even) {
64321             if (oddPath.length) {
64322                 if (!me.gridOdd && oddPath.length) {
64323                     me.gridOdd = surface.add({
64324                         type: 'path',
64325                         path: oddPath
64326                     });
64327                 }
64328                 me.gridOdd.setAttributes(Ext.apply({
64329                     path: oddPath,
64330                     hidden: false
64331                 }, odd || {}), true);
64332             }
64333             if (evenPath.length) {
64334                 if (!me.gridEven) {
64335                     me.gridEven = surface.add({
64336                         type: 'path',
64337                         path: evenPath
64338                     });
64339                 }
64340                 me.gridEven.setAttributes(Ext.apply({
64341                     path: evenPath,
64342                     hidden: false
64343                 }, even || {}), true);
64344             }
64345         }
64346         else {
64347             if (path.length) {
64348                 if (!me.gridLines) {
64349                     me.gridLines = me.chart.surface.add({
64350                         type: 'path',
64351                         path: path,
64352                         "stroke-width": me.lineWidth || 1,
64353                         stroke: me.gridColor || '#ccc'
64354                     });
64355                 }
64356                 me.gridLines.setAttributes({
64357                     hidden: false,
64358                     path: path
64359                 }, true);
64360             }
64361             else if (me.gridLines) {
64362                 me.gridLines.hide(true);
64363             }
64364         }
64365     },
64366
64367     //@private
64368     getOrCreateLabel: function(i, text) {
64369         var me = this,
64370             labelGroup = me.labelGroup,
64371             textLabel = labelGroup.getAt(i),
64372             surface = me.chart.surface;
64373         if (textLabel) {
64374             if (text != textLabel.attr.text) {
64375                 textLabel.setAttributes(Ext.apply({
64376                     text: text
64377                 }, me.label), true);
64378                 textLabel._bbox = textLabel.getBBox();
64379             }
64380         }
64381         else {
64382             textLabel = surface.add(Ext.apply({
64383                 group: labelGroup,
64384                 type: 'text',
64385                 x: 0,
64386                 y: 0,
64387                 text: text
64388             }, me.label));
64389             surface.renderItem(textLabel);
64390             textLabel._bbox = textLabel.getBBox();
64391         }
64392         //get untransformed bounding box
64393         if (me.label.rotation) {
64394             textLabel.setAttributes({
64395                 rotation: {
64396                     degrees: 0
64397                 }
64398             }, true);
64399             textLabel._ubbox = textLabel.getBBox();
64400             textLabel.setAttributes(me.label, true);
64401         } else {
64402             textLabel._ubbox = textLabel._bbox;
64403         }
64404         return textLabel;
64405     },
64406
64407     rect2pointArray: function(sprite) {
64408         var surface = this.chart.surface,
64409             rect = surface.getBBox(sprite, true),
64410             p1 = [rect.x, rect.y],
64411             p1p = p1.slice(),
64412             p2 = [rect.x + rect.width, rect.y],
64413             p2p = p2.slice(),
64414             p3 = [rect.x + rect.width, rect.y + rect.height],
64415             p3p = p3.slice(),
64416             p4 = [rect.x, rect.y + rect.height],
64417             p4p = p4.slice(),
64418             matrix = sprite.matrix;
64419         //transform the points
64420         p1[0] = matrix.x.apply(matrix, p1p);
64421         p1[1] = matrix.y.apply(matrix, p1p);
64422
64423         p2[0] = matrix.x.apply(matrix, p2p);
64424         p2[1] = matrix.y.apply(matrix, p2p);
64425
64426         p3[0] = matrix.x.apply(matrix, p3p);
64427         p3[1] = matrix.y.apply(matrix, p3p);
64428
64429         p4[0] = matrix.x.apply(matrix, p4p);
64430         p4[1] = matrix.y.apply(matrix, p4p);
64431         return [p1, p2, p3, p4];
64432     },
64433
64434     intersect: function(l1, l2) {
64435         var r1 = this.rect2pointArray(l1),
64436             r2 = this.rect2pointArray(l2);
64437         return !!Ext.draw.Draw.intersect(r1, r2).length;
64438     },
64439
64440     drawHorizontalLabels: function() {
64441        var  me = this,
64442             labelConf = me.label,
64443             floor = Math.floor,
64444             max = Math.max,
64445             axes = me.chart.axes,
64446             position = me.position,
64447             inflections = me.inflections,
64448             ln = inflections.length,
64449             labels = me.labels,
64450             labelGroup = me.labelGroup,
64451             maxHeight = 0,
64452             ratio,
64453             gutterY = me.chart.maxGutter[1],
64454             ubbox, bbox, point, prevX, prevLabel,
64455             projectedWidth = 0,
64456             textLabel, attr, textRight, text,
64457             label, last, x, y, i, firstLabel;
64458
64459         last = ln - 1;
64460         //get a reference to the first text label dimensions
64461         point = inflections[0];
64462         firstLabel = me.getOrCreateLabel(0, me.label.renderer(labels[0]));
64463         ratio = Math.floor(Math.abs(Math.sin(labelConf.rotate && (labelConf.rotate.degrees * Math.PI / 180) || 0)));
64464
64465         for (i = 0; i < ln; i++) {
64466             point = inflections[i];
64467             text = me.label.renderer(labels[i]);
64468             textLabel = me.getOrCreateLabel(i, text);
64469             bbox = textLabel._bbox;
64470             maxHeight = max(maxHeight, bbox.height + me.dashSize + me.label.padding);
64471             x = floor(point[0] - (ratio? bbox.height : bbox.width) / 2);
64472             if (me.chart.maxGutter[0] == 0) {
64473                 if (i == 0 && axes.findIndex('position', 'left') == -1) {
64474                     x = point[0];
64475                 }
64476                 else if (i == last && axes.findIndex('position', 'right') == -1) {
64477                     x = point[0] - bbox.width;
64478                 }
64479             }
64480             if (position == 'top') {
64481                 y = point[1] - (me.dashSize * 2) - me.label.padding - (bbox.height / 2);
64482             }
64483             else {
64484                 y = point[1] + (me.dashSize * 2) + me.label.padding + (bbox.height / 2);
64485             }
64486
64487             textLabel.setAttributes({
64488                 hidden: false,
64489                 x: x,
64490                 y: y
64491             }, true);
64492
64493             // Skip label if there isn't available minimum space
64494             if (i != 0 && (me.intersect(textLabel, prevLabel)
64495                 || me.intersect(textLabel, firstLabel))) {
64496                 textLabel.hide(true);
64497                 continue;
64498             }
64499
64500             prevLabel = textLabel;
64501         }
64502
64503         return maxHeight;
64504     },
64505
64506     drawVerticalLabels: function() {
64507         var me = this,
64508             inflections = me.inflections,
64509             position = me.position,
64510             ln = inflections.length,
64511             labels = me.labels,
64512             maxWidth = 0,
64513             max = Math.max,
64514             floor = Math.floor,
64515             ceil = Math.ceil,
64516             axes = me.chart.axes,
64517             gutterY = me.chart.maxGutter[1],
64518             ubbox, bbox, point, prevLabel,
64519             projectedWidth = 0,
64520             textLabel, attr, textRight, text,
64521             label, last, x, y, i;
64522
64523         last = ln;
64524         for (i = 0; i < last; i++) {
64525             point = inflections[i];
64526             text = me.label.renderer(labels[i]);
64527             textLabel = me.getOrCreateLabel(i, text);
64528             bbox = textLabel._bbox;
64529
64530             maxWidth = max(maxWidth, bbox.width + me.dashSize + me.label.padding);
64531             y = point[1];
64532             if (gutterY < bbox.height / 2) {
64533                 if (i == last - 1 && axes.findIndex('position', 'top') == -1) {
64534                     y = me.y - me.length + ceil(bbox.height / 2);
64535                 }
64536                 else if (i == 0 && axes.findIndex('position', 'bottom') == -1) {
64537                     y = me.y - floor(bbox.height / 2);
64538                 }
64539             }
64540             if (position == 'left') {
64541                 x = point[0] - bbox.width - me.dashSize - me.label.padding - 2;
64542             }
64543             else {
64544                 x = point[0] + me.dashSize + me.label.padding + 2;
64545             }
64546             textLabel.setAttributes(Ext.apply({
64547                 hidden: false,
64548                 x: x,
64549                 y: y
64550             }, me.label), true);
64551             // Skip label if there isn't available minimum space
64552             if (i != 0 && me.intersect(textLabel, prevLabel)) {
64553                 textLabel.hide(true);
64554                 continue;
64555             }
64556             prevLabel = textLabel;
64557         }
64558
64559         return maxWidth;
64560     },
64561
64562     /**
64563      * Renders the labels in the axes.
64564      */
64565     drawLabel: function() {
64566         var me = this,
64567             position = me.position,
64568             labelGroup = me.labelGroup,
64569             inflections = me.inflections,
64570             maxWidth = 0,
64571             maxHeight = 0,
64572             ln, i;
64573
64574         if (position == 'left' || position == 'right') {
64575             maxWidth = me.drawVerticalLabels();
64576         } else {
64577             maxHeight = me.drawHorizontalLabels();
64578         }
64579
64580         // Hide unused bars
64581         ln = labelGroup.getCount();
64582         i = inflections.length;
64583         for (; i < ln; i++) {
64584             labelGroup.getAt(i).hide(true);
64585         }
64586
64587         me.bbox = {};
64588         Ext.apply(me.bbox, me.axisBBox);
64589         me.bbox.height = maxHeight;
64590         me.bbox.width = maxWidth;
64591         if (Ext.isString(me.title)) {
64592             me.drawTitle(maxWidth, maxHeight);
64593         }
64594     },
64595
64596     // @private creates the elipsis for the text.
64597     elipsis: function(sprite, text, desiredWidth, minWidth, center) {
64598         var bbox,
64599             x;
64600
64601         if (desiredWidth < minWidth) {
64602             sprite.hide(true);
64603             return false;
64604         }
64605         while (text.length > 4) {
64606             text = text.substr(0, text.length - 4) + "...";
64607             sprite.setAttributes({
64608                 text: text
64609             }, true);
64610             bbox = sprite.getBBox();
64611             if (bbox.width < desiredWidth) {
64612                 if (typeof center == 'number') {
64613                     sprite.setAttributes({
64614                         x: Math.floor(center - (bbox.width / 2))
64615                     }, true);
64616                 }
64617                 break;
64618             }
64619         }
64620         return true;
64621     },
64622
64623     /**
64624      * Updates the {@link #title} of this axis.
64625      * @param {String} title
64626      */
64627     setTitle: function(title) {
64628         this.title = title;
64629         this.drawLabel();
64630     },
64631
64632     // @private draws the title for the axis.
64633     drawTitle: function(maxWidth, maxHeight) {
64634         var me = this,
64635             position = me.position,
64636             surface = me.chart.surface,
64637             displaySprite = me.displaySprite,
64638             title = me.title,
64639             rotate = (position == 'left' || position == 'right'),
64640             x = me.x,
64641             y = me.y,
64642             base, bbox, pad;
64643
64644         if (displaySprite) {
64645             displaySprite.setAttributes({text: title}, true);
64646         } else {
64647             base = {
64648                 type: 'text',
64649                 x: 0,
64650                 y: 0,
64651                 text: title
64652             };
64653             displaySprite = me.displaySprite = surface.add(Ext.apply(base, me.axisTitleStyle, me.labelTitle));
64654             surface.renderItem(displaySprite);
64655         }
64656         bbox = displaySprite.getBBox();
64657         pad = me.dashSize + me.label.padding;
64658
64659         if (rotate) {
64660             y -= ((me.length / 2) - (bbox.height / 2));
64661             if (position == 'left') {
64662                 x -= (maxWidth + pad + (bbox.width / 2));
64663             }
64664             else {
64665                 x += (maxWidth + pad + bbox.width - (bbox.width / 2));
64666             }
64667             me.bbox.width += bbox.width + 10;
64668         }
64669         else {
64670             x += (me.length / 2) - (bbox.width * 0.5);
64671             if (position == 'top') {
64672                 y -= (maxHeight + pad + (bbox.height * 0.3));
64673             }
64674             else {
64675                 y += (maxHeight + pad + (bbox.height * 0.8));
64676             }
64677             me.bbox.height += bbox.height + 10;
64678         }
64679         displaySprite.setAttributes({
64680             translate: {
64681                 x: x,
64682                 y: y
64683             }
64684         }, true);
64685     }
64686 });
64687
64688 /**
64689  * @class Ext.chart.axis.Category
64690  * @extends Ext.chart.axis.Axis
64691  *
64692  * A type of axis that displays items in categories. This axis is generally used to
64693  * display categorical information like names of items, month names, quarters, etc.
64694  * but no quantitative values. For that other type of information `Number`
64695  * axis are more suitable.
64696  *
64697  * As with other axis you can set the position of the axis and its title. For example:
64698  *
64699  *     @example
64700  *     var store = Ext.create('Ext.data.JsonStore', {
64701  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
64702  *         data: [
64703  *             {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
64704  *             {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
64705  *             {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
64706  *             {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
64707  *             {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
64708  *         ]
64709  *     });
64710  *
64711  *     Ext.create('Ext.chart.Chart', {
64712  *         renderTo: Ext.getBody(),
64713  *         width: 500,
64714  *         height: 300,
64715  *         store: store,
64716  *         axes: [{
64717  *             type: 'Numeric',
64718  *             grid: true,
64719  *             position: 'left',
64720  *             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
64721  *             title: 'Sample Values',
64722  *             grid: {
64723  *                 odd: {
64724  *                     opacity: 1,
64725  *                     fill: '#ddd',
64726  *                     stroke: '#bbb',
64727  *                     'stroke-width': 1
64728  *                 }
64729  *             },
64730  *             minimum: 0,
64731  *             adjustMinimumByMajorUnit: 0
64732  *         }, {
64733  *             type: 'Category',
64734  *             position: 'bottom',
64735  *             fields: ['name'],
64736  *             title: 'Sample Metrics',
64737  *             grid: true,
64738  *             label: {
64739  *                 rotate: {
64740  *                     degrees: 315
64741  *                 }
64742  *             }
64743  *         }],
64744  *         series: [{
64745  *             type: 'area',
64746  *             highlight: false,
64747  *             axis: 'left',
64748  *             xField: 'name',
64749  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
64750  *             style: {
64751  *                 opacity: 0.93
64752  *             }
64753  *         }]
64754  *     });
64755  *
64756  * In this example with set the category axis to the bottom of the surface, bound the axis to
64757  * the `name` property and set as title _Month of the Year_.
64758  */
64759 Ext.define('Ext.chart.axis.Category', {
64760
64761     /* Begin Definitions */
64762
64763     extend: 'Ext.chart.axis.Axis',
64764
64765     alternateClassName: 'Ext.chart.CategoryAxis',
64766
64767     alias: 'axis.category',
64768
64769     /* End Definitions */
64770
64771     /**
64772      * A list of category names to display along this axis.
64773      * @property {String} categoryNames
64774      */
64775     categoryNames: null,
64776
64777     /**
64778      * Indicates whether or not to calculate the number of categories (ticks and
64779      * labels) when there is not enough room to display all labels on the axis.
64780      * If set to true, the axis will determine the number of categories to plot.
64781      * If not, all categories will be plotted.
64782      *
64783      * @property calculateCategoryCount
64784      * @type Boolean
64785      */
64786     calculateCategoryCount: false,
64787
64788     // @private creates an array of labels to be used when rendering.
64789     setLabels: function() {
64790         var store = this.chart.store,
64791             fields = this.fields,
64792             ln = fields.length,
64793             i;
64794
64795         this.labels = [];
64796         store.each(function(record) {
64797             for (i = 0; i < ln; i++) {
64798                 this.labels.push(record.get(fields[i]));
64799             }
64800         }, this);
64801     },
64802
64803     // @private calculates labels positions and marker positions for rendering.
64804     applyData: function() {
64805         this.callParent();
64806         this.setLabels();
64807         var count = this.chart.store.getCount();
64808         return {
64809             from: 0,
64810             to: count,
64811             power: 1,
64812             step: 1,
64813             steps: count - 1
64814         };
64815     }
64816 });
64817
64818 /**
64819  * @class Ext.chart.axis.Gauge
64820  * @extends Ext.chart.axis.Abstract
64821  *
64822  * Gauge Axis is the axis to be used with a Gauge series. The Gauge axis
64823  * displays numeric data from an interval defined by the `minimum`, `maximum` and
64824  * `step` configuration properties. The placement of the numeric data can be changed
64825  * by altering the `margin` option that is set to `10` by default.
64826  *
64827  * A possible configuration for this axis would look like:
64828  *
64829  *     axes: [{
64830  *         type: 'gauge',
64831  *         position: 'gauge',
64832  *         minimum: 0,
64833  *         maximum: 100,
64834  *         steps: 10,
64835  *         margin: 7
64836  *     }],
64837  */
64838 Ext.define('Ext.chart.axis.Gauge', {
64839
64840     /* Begin Definitions */
64841
64842     extend: 'Ext.chart.axis.Abstract',
64843
64844     /* End Definitions */
64845
64846     /**
64847      * @cfg {Number} minimum (required)
64848      * The minimum value of the interval to be displayed in the axis.
64849      */
64850
64851     /**
64852      * @cfg {Number} maximum (required)
64853      * The maximum value of the interval to be displayed in the axis.
64854      */
64855
64856     /**
64857      * @cfg {Number} steps (required)
64858      * The number of steps and tick marks to add to the interval.
64859      */
64860
64861     /**
64862      * @cfg {Number} [margin=10]
64863      * The offset positioning of the tick marks and labels in pixels.
64864      */
64865
64866     /**
64867      * @cfg {String} title
64868      * The title for the Axis.
64869      */
64870
64871     position: 'gauge',
64872
64873     alias: 'axis.gauge',
64874
64875     drawAxis: function(init) {
64876         var chart = this.chart,
64877             surface = chart.surface,
64878             bbox = chart.chartBBox,
64879             centerX = bbox.x + (bbox.width / 2),
64880             centerY = bbox.y + bbox.height,
64881             margin = this.margin || 10,
64882             rho = Math.min(bbox.width, 2 * bbox.height) /2 + margin,
64883             sprites = [], sprite,
64884             steps = this.steps,
64885             i, pi = Math.PI,
64886             cos = Math.cos,
64887             sin = Math.sin;
64888
64889         if (this.sprites && !chart.resizing) {
64890             this.drawLabel();
64891             return;
64892         }
64893
64894         if (this.margin >= 0) {
64895             if (!this.sprites) {
64896                 //draw circles
64897                 for (i = 0; i <= steps; i++) {
64898                     sprite = surface.add({
64899                         type: 'path',
64900                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
64901                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
64902                                     'L', centerX + rho * cos(i / steps * pi - pi),
64903                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
64904                         stroke: '#ccc'
64905                     });
64906                     sprite.setAttributes({
64907                         hidden: false
64908                     }, true);
64909                     sprites.push(sprite);
64910                 }
64911             } else {
64912                 sprites = this.sprites;
64913                 //draw circles
64914                 for (i = 0; i <= steps; i++) {
64915                     sprites[i].setAttributes({
64916                         path: ['M', centerX + (rho - margin) * cos(i / steps * pi - pi),
64917                                     centerY + (rho - margin) * sin(i / steps * pi - pi),
64918                                'L', centerX + rho * cos(i / steps * pi - pi),
64919                                     centerY + rho * sin(i / steps * pi - pi), 'Z'],
64920                         stroke: '#ccc'
64921                     }, true);
64922                 }
64923             }
64924         }
64925         this.sprites = sprites;
64926         this.drawLabel();
64927         if (this.title) {
64928             this.drawTitle();
64929         }
64930     },
64931
64932     drawTitle: function() {
64933         var me = this,
64934             chart = me.chart,
64935             surface = chart.surface,
64936             bbox = chart.chartBBox,
64937             labelSprite = me.titleSprite,
64938             labelBBox;
64939
64940         if (!labelSprite) {
64941             me.titleSprite = labelSprite = surface.add({
64942                 type: 'text',
64943                 zIndex: 2
64944             });
64945         }
64946         labelSprite.setAttributes(Ext.apply({
64947             text: me.title
64948         }, me.label || {}), true);
64949         labelBBox = labelSprite.getBBox();
64950         labelSprite.setAttributes({
64951             x: bbox.x + (bbox.width / 2) - (labelBBox.width / 2),
64952             y: bbox.y + bbox.height - (labelBBox.height / 2) - 4
64953         }, true);
64954     },
64955
64956     /**
64957      * Updates the {@link #title} of this axis.
64958      * @param {String} title
64959      */
64960     setTitle: function(title) {
64961         this.title = title;
64962         this.drawTitle();
64963     },
64964
64965     drawLabel: function() {
64966         var chart = this.chart,
64967             surface = chart.surface,
64968             bbox = chart.chartBBox,
64969             centerX = bbox.x + (bbox.width / 2),
64970             centerY = bbox.y + bbox.height,
64971             margin = this.margin || 10,
64972             rho = Math.min(bbox.width, 2 * bbox.height) /2 + 2 * margin,
64973             round = Math.round,
64974             labelArray = [], label,
64975             maxValue = this.maximum || 0,
64976             steps = this.steps, i = 0,
64977             adjY,
64978             pi = Math.PI,
64979             cos = Math.cos,
64980             sin = Math.sin,
64981             labelConf = this.label,
64982             renderer = labelConf.renderer || function(v) { return v; };
64983
64984         if (!this.labelArray) {
64985             //draw scale
64986             for (i = 0; i <= steps; i++) {
64987                 // TODO Adjust for height of text / 2 instead
64988                 adjY = (i === 0 || i === steps) ? 7 : 0;
64989                 label = surface.add({
64990                     type: 'text',
64991                     text: renderer(round(i / steps * maxValue)),
64992                     x: centerX + rho * cos(i / steps * pi - pi),
64993                     y: centerY + rho * sin(i / steps * pi - pi) - adjY,
64994                     'text-anchor': 'middle',
64995                     'stroke-width': 0.2,
64996                     zIndex: 10,
64997                     stroke: '#333'
64998                 });
64999                 label.setAttributes({
65000                     hidden: false
65001                 }, true);
65002                 labelArray.push(label);
65003             }
65004         }
65005         else {
65006             labelArray = this.labelArray;
65007             //draw values
65008             for (i = 0; i <= steps; i++) {
65009                 // TODO Adjust for height of text / 2 instead
65010                 adjY = (i === 0 || i === steps) ? 7 : 0;
65011                 labelArray[i].setAttributes({
65012                     text: renderer(round(i / steps * maxValue)),
65013                     x: centerX + rho * cos(i / steps * pi - pi),
65014                     y: centerY + rho * sin(i / steps * pi - pi) - adjY
65015                 }, true);
65016             }
65017         }
65018         this.labelArray = labelArray;
65019     }
65020 });
65021 /**
65022  * @class Ext.chart.axis.Numeric
65023  * @extends Ext.chart.axis.Axis
65024  *
65025  * An axis to handle numeric values. This axis is used for quantitative data as
65026  * opposed to the category axis. You can set mininum and maximum values to the
65027  * axis so that the values are bound to that. If no values are set, then the
65028  * scale will auto-adjust to the values.
65029  *
65030  *     @example
65031  *     var store = Ext.create('Ext.data.JsonStore', {
65032  *          fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
65033  *          data: [
65034  *              {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
65035  *              {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
65036  *              {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
65037  *              {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
65038  *              {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
65039  *          ]
65040  *     });
65041  *
65042  *     Ext.create('Ext.chart.Chart', {
65043  *         renderTo: Ext.getBody(),
65044  *         width: 500,
65045  *         height: 300,
65046  *         store: store,
65047  *         axes: [{
65048  *             type: 'Numeric',
65049  *             grid: true,
65050  *             position: 'left',
65051  *             fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
65052  *             title: 'Sample Values',
65053  *             grid: {
65054  *                 odd: {
65055  *                     opacity: 1,
65056  *                     fill: '#ddd',
65057  *                     stroke: '#bbb',
65058  *                     'stroke-width': 1
65059  *                 }
65060  *             },
65061  *             minimum: 0,
65062  *             adjustMinimumByMajorUnit: 0
65063  *         }, {
65064  *             type: 'Category',
65065  *             position: 'bottom',
65066  *             fields: ['name'],
65067  *             title: 'Sample Metrics',
65068  *             grid: true,
65069  *             label: {
65070  *                 rotate: {
65071  *                     degrees: 315
65072  *                 }
65073  *             }
65074  *         }],
65075  *         series: [{
65076  *             type: 'area',
65077  *             highlight: false,
65078  *             axis: 'left',
65079  *             xField: 'name',
65080  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
65081  *             style: {
65082  *                 opacity: 0.93
65083  *             }
65084  *         }]
65085  *     });
65086  *
65087  * In this example we create an axis of Numeric type. We set a minimum value so that
65088  * even if all series have values greater than zero, the grid starts at zero. We bind
65089  * the axis onto the left part of the surface by setting `position` to `left`.
65090  * We bind three different store fields to this axis by setting `fields` to an array.
65091  * We set the title of the axis to _Number of Hits_ by using the `title` property.
65092  * We use a `grid` configuration to set odd background rows to a certain style and even rows
65093  * to be transparent/ignored.
65094  */
65095 Ext.define('Ext.chart.axis.Numeric', {
65096
65097     /* Begin Definitions */
65098
65099     extend: 'Ext.chart.axis.Axis',
65100
65101     alternateClassName: 'Ext.chart.NumericAxis',
65102
65103     /* End Definitions */
65104
65105     type: 'numeric',
65106
65107     alias: 'axis.numeric',
65108
65109     constructor: function(config) {
65110         var me = this,
65111             hasLabel = !!(config.label && config.label.renderer),
65112             label;
65113
65114         me.callParent([config]);
65115         label = me.label;
65116         if (me.roundToDecimal === false) {
65117             return;
65118         }
65119         if (!hasLabel) {
65120             label.renderer = function(v) {
65121                 return me.roundToDecimal(v, me.decimals);
65122             };
65123         }
65124     },
65125
65126     roundToDecimal: function(v, dec) {
65127         var val = Math.pow(10, dec || 0);
65128         return Math.floor(v * val) / val;
65129     },
65130
65131     /**
65132      * The minimum value drawn by the axis. If not set explicitly, the axis
65133      * minimum will be calculated automatically.
65134      *
65135      * @property {Number} minimum
65136      */
65137     minimum: NaN,
65138
65139     /**
65140      * The maximum value drawn by the axis. If not set explicitly, the axis
65141      * maximum will be calculated automatically.
65142      *
65143      * @property {Number} maximum
65144      */
65145     maximum: NaN,
65146
65147     /**
65148      * The number of decimals to round the value to.
65149      *
65150      * @property {Number} decimals
65151      */
65152     decimals: 2,
65153
65154     /**
65155      * The scaling algorithm to use on this axis. May be "linear" or
65156      * "logarithmic".  Currently only linear scale is implemented.
65157      *
65158      * @property {String} scale
65159      * @private
65160      */
65161     scale: "linear",
65162
65163     /**
65164      * Indicates the position of the axis relative to the chart
65165      *
65166      * @property {String} position
65167      */
65168     position: 'left',
65169
65170     /**
65171      * Indicates whether to extend maximum beyond data's maximum to the nearest
65172      * majorUnit.
65173      *
65174      * @property {Boolean} adjustMaximumByMajorUnit
65175      */
65176     adjustMaximumByMajorUnit: false,
65177
65178     /**
65179      * Indicates whether to extend the minimum beyond data's minimum to the
65180      * nearest majorUnit.
65181      *
65182      * @property {Boolean} adjustMinimumByMajorUnit
65183      */
65184     adjustMinimumByMajorUnit: false,
65185
65186     // @private apply data.
65187     applyData: function() {
65188         this.callParent();
65189         return this.calcEnds();
65190     }
65191 });
65192
65193 /**
65194  * @class Ext.chart.axis.Radial
65195  * @extends Ext.chart.axis.Abstract
65196  * @ignore
65197  */
65198 Ext.define('Ext.chart.axis.Radial', {
65199
65200     /* Begin Definitions */
65201
65202     extend: 'Ext.chart.axis.Abstract',
65203
65204     /* End Definitions */
65205
65206     position: 'radial',
65207
65208     alias: 'axis.radial',
65209
65210     drawAxis: function(init) {
65211         var chart = this.chart,
65212             surface = chart.surface,
65213             bbox = chart.chartBBox,
65214             store = chart.store,
65215             l = store.getCount(),
65216             centerX = bbox.x + (bbox.width / 2),
65217             centerY = bbox.y + (bbox.height / 2),
65218             rho = Math.min(bbox.width, bbox.height) /2,
65219             sprites = [], sprite,
65220             steps = this.steps,
65221             i, j, pi2 = Math.PI * 2,
65222             cos = Math.cos, sin = Math.sin;
65223
65224         if (this.sprites && !chart.resizing) {
65225             this.drawLabel();
65226             return;
65227         }
65228
65229         if (!this.sprites) {
65230             //draw circles
65231             for (i = 1; i <= steps; i++) {
65232                 sprite = surface.add({
65233                     type: 'circle',
65234                     x: centerX,
65235                     y: centerY,
65236                     radius: Math.max(rho * i / steps, 0),
65237                     stroke: '#ccc'
65238                 });
65239                 sprite.setAttributes({
65240                     hidden: false
65241                 }, true);
65242                 sprites.push(sprite);
65243             }
65244             //draw lines
65245             store.each(function(rec, i) {
65246                 sprite = surface.add({
65247                     type: 'path',
65248                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(i / l * pi2), centerY + rho * sin(i / l * pi2), 'Z'],
65249                     stroke: '#ccc'
65250                 });
65251                 sprite.setAttributes({
65252                     hidden: false
65253                 }, true);
65254                 sprites.push(sprite);
65255             });
65256         } else {
65257             sprites = this.sprites;
65258             //draw circles
65259             for (i = 0; i < steps; i++) {
65260                 sprites[i].setAttributes({
65261                     x: centerX,
65262                     y: centerY,
65263                     radius: Math.max(rho * (i + 1) / steps, 0),
65264                     stroke: '#ccc'
65265                 }, true);
65266             }
65267             //draw lines
65268             store.each(function(rec, j) {
65269                 sprites[i + j].setAttributes({
65270                     path: ['M', centerX, centerY, 'L', centerX + rho * cos(j / l * pi2), centerY + rho * sin(j / l * pi2), 'Z'],
65271                     stroke: '#ccc'
65272                 }, true);
65273             });
65274         }
65275         this.sprites = sprites;
65276
65277         this.drawLabel();
65278     },
65279
65280     drawLabel: function() {
65281         var chart = this.chart,
65282             surface = chart.surface,
65283             bbox = chart.chartBBox,
65284             store = chart.store,
65285             centerX = bbox.x + (bbox.width / 2),
65286             centerY = bbox.y + (bbox.height / 2),
65287             rho = Math.min(bbox.width, bbox.height) /2,
65288             max = Math.max, round = Math.round,
65289             labelArray = [], label,
65290             fields = [], nfields,
65291             categories = [], xField,
65292             aggregate = !this.maximum,
65293             maxValue = this.maximum || 0,
65294             steps = this.steps, i = 0, j, dx, dy,
65295             pi2 = Math.PI * 2,
65296             cos = Math.cos, sin = Math.sin,
65297             display = this.label.display,
65298             draw = display !== 'none',
65299             margin = 10;
65300
65301         if (!draw) {
65302             return;
65303         }
65304
65305         //get all rendered fields
65306         chart.series.each(function(series) {
65307             fields.push(series.yField);
65308             xField = series.xField;
65309         });
65310         
65311         //get maxValue to interpolate
65312         store.each(function(record, i) {
65313             if (aggregate) {
65314                 for (i = 0, nfields = fields.length; i < nfields; i++) {
65315                     maxValue = max(+record.get(fields[i]), maxValue);
65316                 }
65317             }
65318             categories.push(record.get(xField));
65319         });
65320         if (!this.labelArray) {
65321             if (display != 'categories') {
65322                 //draw scale
65323                 for (i = 1; i <= steps; i++) {
65324                     label = surface.add({
65325                         type: 'text',
65326                         text: round(i / steps * maxValue),
65327                         x: centerX,
65328                         y: centerY - rho * i / steps,
65329                         'text-anchor': 'middle',
65330                         'stroke-width': 0.1,
65331                         stroke: '#333'
65332                     });
65333                     label.setAttributes({
65334                         hidden: false
65335                     }, true);
65336                     labelArray.push(label);
65337                 }
65338             }
65339             if (display != 'scale') {
65340                 //draw text
65341                 for (j = 0, steps = categories.length; j < steps; j++) {
65342                     dx = cos(j / steps * pi2) * (rho + margin);
65343                     dy = sin(j / steps * pi2) * (rho + margin);
65344                     label = surface.add({
65345                         type: 'text',
65346                         text: categories[j],
65347                         x: centerX + dx,
65348                         y: centerY + dy,
65349                         'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
65350                     });
65351                     label.setAttributes({
65352                         hidden: false
65353                     }, true);
65354                     labelArray.push(label);
65355                 }
65356             }
65357         }
65358         else {
65359             labelArray = this.labelArray;
65360             if (display != 'categories') {
65361                 //draw values
65362                 for (i = 0; i < steps; i++) {
65363                     labelArray[i].setAttributes({
65364                         text: round((i + 1) / steps * maxValue),
65365                         x: centerX,
65366                         y: centerY - rho * (i + 1) / steps,
65367                         'text-anchor': 'middle',
65368                         'stroke-width': 0.1,
65369                         stroke: '#333'
65370                     }, true);
65371                 }
65372             }
65373             if (display != 'scale') {
65374                 //draw text
65375                 for (j = 0, steps = categories.length; j < steps; j++) {
65376                     dx = cos(j / steps * pi2) * (rho + margin);
65377                     dy = sin(j / steps * pi2) * (rho + margin);
65378                     if (labelArray[i + j]) {
65379                         labelArray[i + j].setAttributes({
65380                             type: 'text',
65381                             text: categories[j],
65382                             x: centerX + dx,
65383                             y: centerY + dy,
65384                             'text-anchor': dx * dx <= 0.001? 'middle' : (dx < 0? 'end' : 'start')
65385                         }, true);
65386                     }
65387                 }
65388             }
65389         }
65390         this.labelArray = labelArray;
65391     }
65392 });
65393 /**
65394  * @author Ed Spencer
65395  *
65396  * AbstractStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.TreeStore}. It's never used directly,
65397  * but offers a set of methods used by both of those subclasses.
65398  * 
65399  * We've left it here in the docs for reference purposes, but unless you need to make a whole new type of Store, what
65400  * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what 
65401  * AbstractStore is and is not.
65402  * 
65403  * AbstractStore provides the basic configuration for anything that can be considered a Store. It expects to be 
65404  * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a 
65405  * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.
65406  * 
65407  * AbstractStore provides a few helpful methods such as {@link #load} and {@link #sync}, which load and save data
65408  * respectively, passing the requests through the configured {@link #proxy}. Both built-in Store subclasses add extra
65409  * behavior to each of these functions. Note also that each AbstractStore subclass has its own way of storing data - 
65410  * in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.MixedCollection MixedCollection}, whereas in
65411  * {@link Ext.data.TreeStore TreeStore} we use a {@link Ext.data.Tree} to maintain the data's hierarchy.
65412  * 
65413  * The store provides filtering and sorting support. This sorting/filtering can happen on the client side
65414  * or can be completed on the server. This is controlled by the {@link Ext.data.Store#remoteSort remoteSort} and
65415  * {@link Ext.data.Store#remoteFilter remoteFilter} config options. For more information see the {@link #sort} and
65416  * {@link Ext.data.Store#filter filter} methods.
65417  */
65418 Ext.define('Ext.data.AbstractStore', {
65419     requires: ['Ext.util.MixedCollection', 'Ext.data.Operation', 'Ext.util.Filter'],
65420     
65421     mixins: {
65422         observable: 'Ext.util.Observable',
65423         sortable: 'Ext.util.Sortable'
65424     },
65425     
65426     statics: {
65427         create: function(store){
65428             if (!store.isStore) {
65429                 if (!store.type) {
65430                     store.type = 'store';
65431                 }
65432                 store = Ext.createByAlias('store.' + store.type, store);
65433             }
65434             return store;
65435         }    
65436     },
65437     
65438     remoteSort  : false,
65439     remoteFilter: false,
65440
65441     /**
65442      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy
65443      * The Proxy to use for this Store. This can be either a string, a config object or a Proxy instance -
65444      * see {@link #setProxy} for details.
65445      */
65446
65447     /**
65448      * @cfg {Boolean/Object} autoLoad
65449      * If data is not specified, and if autoLoad is true or an Object, this store's load method is automatically called
65450      * after creation. If the value of autoLoad is an Object, this Object will be passed to the store's load method.
65451      * Defaults to false.
65452      */
65453     autoLoad: false,
65454
65455     /**
65456      * @cfg {Boolean} autoSync
65457      * True to automatically sync the Store with its Proxy after every edit to one of its Records. Defaults to false.
65458      */
65459     autoSync: false,
65460
65461     /**
65462      * @property {String} batchUpdateMode
65463      * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's
65464      * internal representation of the data after each operation of the batch has completed, 'complete' will wait until
65465      * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local
65466      * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.
65467      */
65468     batchUpdateMode: 'operation',
65469
65470     /**
65471      * @property {Boolean} filterOnLoad
65472      * If true, any filters attached to this Store will be run after loading data, before the datachanged event is fired.
65473      * Defaults to true, ignored if {@link Ext.data.Store#remoteFilter remoteFilter} is true
65474      */
65475     filterOnLoad: true,
65476
65477     /**
65478      * @property {Boolean} sortOnLoad
65479      * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.
65480      * Defaults to true, igored if {@link Ext.data.Store#remoteSort remoteSort} is true
65481      */
65482     sortOnLoad: true,
65483
65484     /**
65485      * @property {Boolean} implicitModel
65486      * True if a model was created implicitly for this Store. This happens if a fields array is passed to the Store's
65487      * constructor instead of a model constructor or name.
65488      * @private
65489      */
65490     implicitModel: false,
65491
65492     /**
65493      * @property {String} defaultProxyType
65494      * The string type of the Proxy to create if none is specified. This defaults to creating a
65495      * {@link Ext.data.proxy.Memory memory proxy}.
65496      */
65497     defaultProxyType: 'memory',
65498
65499     /**
65500      * @property {Boolean} isDestroyed
65501      * True if the Store has already been destroyed. If this is true, the reference to Store should be deleted
65502      * as it will not function correctly any more.
65503      */
65504     isDestroyed: false,
65505
65506     isStore: true,
65507
65508     /**
65509      * @cfg {String} storeId
65510      * Unique identifier for this store. If present, this Store will be registered with the {@link Ext.data.StoreManager},
65511      * making it easy to reuse elsewhere. Defaults to undefined.
65512      */
65513     
65514     /**
65515      * @cfg {Object[]} fields
65516      * This may be used in place of specifying a {@link #model} configuration. The fields should be a 
65517      * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
65518      * with these fields. In general this configuration option should be avoided, it exists for the purposes of
65519      * backwards compatibility. For anything more complicated, such as specifying a particular id property or
65520      * assocations, a {@link Ext.data.Model} should be defined and specified for the {@link #model}
65521      * config.
65522      */
65523
65524     /**
65525      * @cfg {String} model
65526      * Name of the {@link Ext.data.Model Model} associated with this store.
65527      * The string is used as an argument for {@link Ext.ModelManager#getModel}.
65528      */
65529
65530     sortRoot: 'data',
65531     
65532     //documented above
65533     constructor: function(config) {
65534         var me = this,
65535             filters;
65536         
65537         me.addEvents(
65538             /**
65539              * @event add
65540              * Fired when a Model instance has been added to this Store
65541              * @param {Ext.data.Store} store The store
65542              * @param {Ext.data.Model[]} records The Model instances that were added
65543              * @param {Number} index The index at which the instances were inserted
65544              */
65545             'add',
65546
65547             /**
65548              * @event remove
65549              * Fired when a Model instance has been removed from this Store
65550              * @param {Ext.data.Store} store The Store object
65551              * @param {Ext.data.Model} record The record that was removed
65552              * @param {Number} index The index of the record that was removed
65553              */
65554             'remove',
65555             
65556             /**
65557              * @event update
65558              * Fires when a Model instance has been updated
65559              * @param {Ext.data.Store} this
65560              * @param {Ext.data.Model} record The Model instance that was updated
65561              * @param {String} operation The update operation being performed. Value may be one of:
65562              *
65563              *     Ext.data.Model.EDIT
65564              *     Ext.data.Model.REJECT
65565              *     Ext.data.Model.COMMIT
65566              */
65567             'update',
65568
65569             /**
65570              * @event datachanged
65571              * Fires whenever the records in the Store have changed in some way - this could include adding or removing
65572              * records, or updating the data in existing records
65573              * @param {Ext.data.Store} this The data store
65574              */
65575             'datachanged',
65576
65577             /**
65578              * @event beforeload
65579              * Fires before a request is made for a new data object. If the beforeload handler returns false the load
65580              * action will be canceled.
65581              * @param {Ext.data.Store} store This Store
65582              * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
65583              * load the Store
65584              */
65585             'beforeload',
65586
65587             /**
65588              * @event load
65589              * Fires whenever the store reads data from a remote data source.
65590              * @param {Ext.data.Store} this
65591              * @param {Ext.data.Model[]} records An array of records
65592              * @param {Boolean} successful True if the operation was successful.
65593              */
65594             'load',
65595             
65596             /**
65597              * @event write
65598              * Fires whenever a successful write has been made via the configured {@link #proxy Proxy}
65599              * @param {Ext.data.Store} store This Store
65600              * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object that was used in
65601              * the write
65602              */
65603             'write',
65604
65605             /**
65606              * @event beforesync
65607              * Fired before a call to {@link #sync} is executed. Return false from any listener to cancel the synv
65608              * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
65609              */
65610             'beforesync',
65611             /**
65612              * @event clear
65613              * Fired after the {@link #removeAll} method is called.
65614              * @param {Ext.data.Store} this
65615              */
65616             'clear'
65617         );
65618         
65619         Ext.apply(me, config);
65620         // don't use *config* anymore from here on... use *me* instead...
65621
65622         /**
65623          * Temporary cache in which removed model instances are kept until successfully synchronised with a Proxy,
65624          * at which point this is cleared.
65625          * @private
65626          * @property {Ext.data.Model[]} removed
65627          */
65628         me.removed = [];
65629
65630         me.mixins.observable.constructor.apply(me, arguments);
65631         me.model = Ext.ModelManager.getModel(me.model);
65632
65633         /**
65634          * @property {Object} modelDefaults
65635          * @private
65636          * A set of default values to be applied to every model instance added via {@link #insert} or created via {@link #create}.
65637          * This is used internally by associations to set foreign keys and other fields. See the Association classes source code
65638          * for examples. This should not need to be used by application developers.
65639          */
65640         Ext.applyIf(me, {
65641             modelDefaults: {}
65642         });
65643
65644         //Supports the 3.x style of simply passing an array of fields to the store, implicitly creating a model
65645         if (!me.model && me.fields) {
65646             me.model = Ext.define('Ext.data.Store.ImplicitModel-' + (me.storeId || Ext.id()), {
65647                 extend: 'Ext.data.Model',
65648                 fields: me.fields,
65649                 proxy: me.proxy || me.defaultProxyType
65650             });
65651
65652             delete me.fields;
65653
65654             me.implicitModel = true;
65655         }
65656         
65657
65658         //ensures that the Proxy is instantiated correctly
65659         me.setProxy(me.proxy || me.model.getProxy());
65660
65661         if (me.id && !me.storeId) {
65662             me.storeId = me.id;
65663             delete me.id;
65664         }
65665
65666         if (me.storeId) {
65667             Ext.data.StoreManager.register(me);
65668         }
65669         
65670         me.mixins.sortable.initSortable.call(me);        
65671         
65672         /**
65673          * @property {Ext.util.MixedCollection} filters
65674          * The collection of {@link Ext.util.Filter Filters} currently applied to this Store
65675          */
65676         filters = me.decodeFilters(me.filters);
65677         me.filters = Ext.create('Ext.util.MixedCollection');
65678         me.filters.addAll(filters);
65679     },
65680
65681     /**
65682      * Sets the Store's Proxy by string, config object or Proxy instance
65683      * @param {String/Object/Ext.data.proxy.Proxy} proxy The new Proxy, which can be either a type string, a configuration object
65684      * or an Ext.data.proxy.Proxy instance
65685      * @return {Ext.data.proxy.Proxy} The attached Proxy object
65686      */
65687     setProxy: function(proxy) {
65688         var me = this;
65689         
65690         if (proxy instanceof Ext.data.proxy.Proxy) {
65691             proxy.setModel(me.model);
65692         } else {
65693             if (Ext.isString(proxy)) {
65694                 proxy = {
65695                     type: proxy    
65696                 };
65697             }
65698             Ext.applyIf(proxy, {
65699                 model: me.model
65700             });
65701             
65702             proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);
65703         }
65704         
65705         me.proxy = proxy;
65706         
65707         return me.proxy;
65708     },
65709
65710     /**
65711      * Returns the proxy currently attached to this proxy instance
65712      * @return {Ext.data.proxy.Proxy} The Proxy instance
65713      */
65714     getProxy: function() {
65715         return this.proxy;
65716     },
65717
65718     //saves any phantom records
65719     create: function(data, options) {
65720         var me = this,
65721             instance = Ext.ModelManager.create(Ext.applyIf(data, me.modelDefaults), me.model.modelName),
65722             operation;
65723         
65724         options = options || {};
65725
65726         Ext.applyIf(options, {
65727             action : 'create',
65728             records: [instance]
65729         });
65730
65731         operation = Ext.create('Ext.data.Operation', options);
65732
65733         me.proxy.create(operation, me.onProxyWrite, me);
65734         
65735         return instance;
65736     },
65737
65738     read: function() {
65739         return this.load.apply(this, arguments);
65740     },
65741
65742     onProxyRead: Ext.emptyFn,
65743
65744     update: function(options) {
65745         var me = this,
65746             operation;
65747         options = options || {};
65748
65749         Ext.applyIf(options, {
65750             action : 'update',
65751             records: me.getUpdatedRecords()
65752         });
65753
65754         operation = Ext.create('Ext.data.Operation', options);
65755
65756         return me.proxy.update(operation, me.onProxyWrite, me);
65757     },
65758
65759     /**
65760      * @private
65761      * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
65762      * the updates provided by the Proxy
65763      */
65764     onProxyWrite: function(operation) {
65765         var me = this,
65766             success = operation.wasSuccessful(),
65767             records = operation.getRecords();
65768
65769         switch (operation.action) {
65770             case 'create':
65771                 me.onCreateRecords(records, operation, success);
65772                 break;
65773             case 'update':
65774                 me.onUpdateRecords(records, operation, success);
65775                 break;
65776             case 'destroy':
65777                 me.onDestroyRecords(records, operation, success);
65778                 break;
65779         }
65780
65781         if (success) {
65782             me.fireEvent('write', me, operation);
65783             me.fireEvent('datachanged', me);
65784         }
65785         //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
65786         Ext.callback(operation.callback, operation.scope || me, [records, operation, success]);
65787     },
65788
65789
65790     //tells the attached proxy to destroy the given records
65791     destroy: function(options) {
65792         var me = this,
65793             operation;
65794             
65795         options = options || {};
65796
65797         Ext.applyIf(options, {
65798             action : 'destroy',
65799             records: me.getRemovedRecords()
65800         });
65801
65802         operation = Ext.create('Ext.data.Operation', options);
65803
65804         return me.proxy.destroy(operation, me.onProxyWrite, me);
65805     },
65806
65807     /**
65808      * @private
65809      * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through
65810      * to onProxyWrite.
65811      */
65812     onBatchOperationComplete: function(batch, operation) {
65813         return this.onProxyWrite(operation);
65814     },
65815
65816     /**
65817      * @private
65818      * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations
65819      * and updates the Store's internal data MixedCollection.
65820      */
65821     onBatchComplete: function(batch, operation) {
65822         var me = this,
65823             operations = batch.operations,
65824             length = operations.length,
65825             i;
65826
65827         me.suspendEvents();
65828
65829         for (i = 0; i < length; i++) {
65830             me.onProxyWrite(operations[i]);
65831         }
65832
65833         me.resumeEvents();
65834
65835         me.fireEvent('datachanged', me);
65836     },
65837
65838     onBatchException: function(batch, operation) {
65839         // //decide what to do... could continue with the next operation
65840         // batch.start();
65841         //
65842         // //or retry the last operation
65843         // batch.retry();
65844     },
65845
65846     /**
65847      * @private
65848      * Filter function for new records.
65849      */
65850     filterNew: function(item) {
65851         // only want phantom records that are valid
65852         return item.phantom === true && item.isValid();
65853     },
65854
65855     /**
65856      * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
65857      * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one)
65858      * @return {Ext.data.Model[]} The Model instances
65859      */
65860     getNewRecords: function() {
65861         return [];
65862     },
65863
65864     /**
65865      * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy
65866      * @return {Ext.data.Model[]} The updated Model instances
65867      */
65868     getUpdatedRecords: function() {
65869         return [];
65870     },
65871
65872     /**
65873      * @private
65874      * Filter function for updated records.
65875      */
65876     filterUpdated: function(item) {
65877         // only want dirty records, not phantoms that are valid
65878         return item.dirty === true && item.phantom !== true && item.isValid();
65879     },
65880
65881     /**
65882      * Returns any records that have been removed from the store but not yet destroyed on the proxy.
65883      * @return {Ext.data.Model[]} The removed Model instances
65884      */
65885     getRemovedRecords: function() {
65886         return this.removed;
65887     },
65888
65889     filter: function(filters, value) {
65890
65891     },
65892
65893     /**
65894      * @private
65895      * Normalizes an array of filter objects, ensuring that they are all Ext.util.Filter instances
65896      * @param {Object[]} filters The filters array
65897      * @return {Ext.util.Filter[]} Array of Ext.util.Filter objects
65898      */
65899     decodeFilters: function(filters) {
65900         if (!Ext.isArray(filters)) {
65901             if (filters === undefined) {
65902                 filters = [];
65903             } else {
65904                 filters = [filters];
65905             }
65906         }
65907
65908         var length = filters.length,
65909             Filter = Ext.util.Filter,
65910             config, i;
65911
65912         for (i = 0; i < length; i++) {
65913             config = filters[i];
65914
65915             if (!(config instanceof Filter)) {
65916                 Ext.apply(config, {
65917                     root: 'data'
65918                 });
65919
65920                 //support for 3.x style filters where a function can be defined as 'fn'
65921                 if (config.fn) {
65922                     config.filterFn = config.fn;
65923                 }
65924
65925                 //support a function to be passed as a filter definition
65926                 if (typeof config == 'function') {
65927                     config = {
65928                         filterFn: config
65929                     };
65930                 }
65931
65932                 filters[i] = new Filter(config);
65933             }
65934         }
65935
65936         return filters;
65937     },
65938
65939     clearFilter: function(supressEvent) {
65940
65941     },
65942
65943     isFiltered: function() {
65944
65945     },
65946
65947     filterBy: function(fn, scope) {
65948
65949     },
65950     
65951     /**
65952      * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
65953      * and deleted records in the store, updating the Store's internal representation of the records
65954      * as each operation completes.
65955      */
65956     sync: function() {
65957         var me        = this,
65958             options   = {},
65959             toCreate  = me.getNewRecords(),
65960             toUpdate  = me.getUpdatedRecords(),
65961             toDestroy = me.getRemovedRecords(),
65962             needsSync = false;
65963
65964         if (toCreate.length > 0) {
65965             options.create = toCreate;
65966             needsSync = true;
65967         }
65968
65969         if (toUpdate.length > 0) {
65970             options.update = toUpdate;
65971             needsSync = true;
65972         }
65973
65974         if (toDestroy.length > 0) {
65975             options.destroy = toDestroy;
65976             needsSync = true;
65977         }
65978
65979         if (needsSync && me.fireEvent('beforesync', options) !== false) {
65980             me.proxy.batch(options, me.getBatchListeners());
65981         }
65982     },
65983
65984
65985     /**
65986      * @private
65987      * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.
65988      * This is broken out into a separate function to allow for customisation of the listeners
65989      * @return {Object} The listeners object
65990      */
65991     getBatchListeners: function() {
65992         var me = this,
65993             listeners = {
65994                 scope: me,
65995                 exception: me.onBatchException
65996             };
65997
65998         if (me.batchUpdateMode == 'operation') {
65999             listeners.operationcomplete = me.onBatchOperationComplete;
66000         } else {
66001             listeners.complete = me.onBatchComplete;
66002         }
66003
66004         return listeners;
66005     },
66006
66007     //deprecated, will be removed in 5.0
66008     save: function() {
66009         return this.sync.apply(this, arguments);
66010     },
66011
66012     /**
66013      * Loads the Store using its configured {@link #proxy}.
66014      * @param {Object} options (optional) config object. This is passed into the {@link Ext.data.Operation Operation}
66015      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function
66016      */
66017     load: function(options) {
66018         var me = this,
66019             operation;
66020
66021         options = options || {};
66022
66023         Ext.applyIf(options, {
66024             action : 'read',
66025             filters: me.filters.items,
66026             sorters: me.getSorters()
66027         });
66028         
66029         operation = Ext.create('Ext.data.Operation', options);
66030
66031         if (me.fireEvent('beforeload', me, operation) !== false) {
66032             me.loading = true;
66033             me.proxy.read(operation, me.onProxyLoad, me);
66034         }
66035         
66036         return me;
66037     },
66038
66039     /**
66040      * @private
66041      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
66042      * @param {Ext.data.Model} record The model instance that was edited
66043      */
66044     afterEdit : function(record) {
66045         var me = this;
66046         
66047         if (me.autoSync) {
66048             me.sync();
66049         }
66050         
66051         me.fireEvent('update', me, record, Ext.data.Model.EDIT);
66052     },
66053
66054     /**
66055      * @private
66056      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..
66057      * @param {Ext.data.Model} record The model instance that was edited
66058      */
66059     afterReject : function(record) {
66060         this.fireEvent('update', this, record, Ext.data.Model.REJECT);
66061     },
66062
66063     /**
66064      * @private
66065      * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
66066      * @param {Ext.data.Model} record The model instance that was edited
66067      */
66068     afterCommit : function(record) {
66069         this.fireEvent('update', this, record, Ext.data.Model.COMMIT);
66070     },
66071
66072     clearData: Ext.emptyFn,
66073
66074     destroyStore: function() {
66075         var me = this;
66076         
66077         if (!me.isDestroyed) {
66078             if (me.storeId) {
66079                 Ext.data.StoreManager.unregister(me);
66080             }
66081             me.clearData();
66082             me.data = null;
66083             me.tree = null;
66084             // Ext.destroy(this.proxy);
66085             me.reader = me.writer = null;
66086             me.clearListeners();
66087             me.isDestroyed = true;
66088
66089             if (me.implicitModel) {
66090                 Ext.destroy(me.model);
66091             }
66092         }
66093     },
66094     
66095     doSort: function(sorterFn) {
66096         var me = this;
66097         if (me.remoteSort) {
66098             //the load function will pick up the new sorters and request the sorted data from the proxy
66099             me.load();
66100         } else {
66101             me.data.sortBy(sorterFn);
66102             me.fireEvent('datachanged', me);
66103         }
66104     },
66105
66106     getCount: Ext.emptyFn,
66107
66108     getById: Ext.emptyFn,
66109     
66110     /**
66111      * Removes all records from the store. This method does a "fast remove",
66112      * individual remove events are not called. The {@link #clear} event is
66113      * fired upon completion.
66114      * @method
66115      */
66116     removeAll: Ext.emptyFn,
66117     // individual substores should implement a "fast" remove
66118     // and fire a clear event afterwards
66119
66120     /**
66121      * Returns true if the Store is currently performing a load operation
66122      * @return {Boolean} True if the Store is currently loading
66123      */
66124     isLoading: function() {
66125         return !!this.loading;
66126      }
66127 });
66128
66129 /**
66130  * @class Ext.util.Grouper
66131  * @extends Ext.util.Sorter
66132
66133 Represents a single grouper that can be applied to a Store. The grouper works
66134 in the same fashion as the {@link Ext.util.Sorter}.
66135
66136  * @markdown
66137  */
66138  
66139 Ext.define('Ext.util.Grouper', {
66140
66141     /* Begin Definitions */
66142
66143     extend: 'Ext.util.Sorter',
66144
66145     /* End Definitions */
66146
66147     /**
66148      * Returns the value for grouping to be used.
66149      * @param {Ext.data.Model} instance The Model instance
66150      * @return {String} The group string for this model
66151      */
66152     getGroupString: function(instance) {
66153         return instance.get(this.property);
66154     }
66155 });
66156 /**
66157  * @author Ed Spencer
66158  * @class Ext.data.Store
66159  * @extends Ext.data.AbstractStore
66160  *
66161  * <p>The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load
66162  * data via a {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting},
66163  * {@link #filter filtering} and querying the {@link Ext.data.Model model} instances contained within it.</p>
66164  *
66165  * <p>Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:</p>
66166  *
66167 <pre><code>
66168 // Set up a {@link Ext.data.Model model} to use in our Store
66169 Ext.define('User', {
66170     extend: 'Ext.data.Model',
66171     fields: [
66172         {name: 'firstName', type: 'string'},
66173         {name: 'lastName',  type: 'string'},
66174         {name: 'age',       type: 'int'},
66175         {name: 'eyeColor',  type: 'string'}
66176     ]
66177 });
66178
66179 var myStore = Ext.create('Ext.data.Store', {
66180     model: 'User',
66181     proxy: {
66182         type: 'ajax',
66183         url : '/users.json',
66184         reader: {
66185             type: 'json',
66186             root: 'users'
66187         }
66188     },
66189     autoLoad: true
66190 });
66191 </code></pre>
66192
66193  * <p>In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy
66194  * to use a {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object -
66195  * {@link Ext.data.reader.Json see the docs on JsonReader} for details.</p>
66196  *
66197  * <p><u>Inline data</u></p>
66198  *
66199  * <p>Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #data}
66200  * into Model instances:</p>
66201  *
66202 <pre><code>
66203 Ext.create('Ext.data.Store', {
66204     model: 'User',
66205     data : [
66206         {firstName: 'Ed',    lastName: 'Spencer'},
66207         {firstName: 'Tommy', lastName: 'Maintz'},
66208         {firstName: 'Aaron', lastName: 'Conran'},
66209         {firstName: 'Jamie', lastName: 'Avins'}
66210     ]
66211 });
66212 </code></pre>
66213  *
66214  * <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
66215  * to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode the data structure,
66216  * use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory MemoryProxy} docs for an example).</p>
66217  *
66218  * <p>Additional data can also be loaded locally using {@link #add}.</p>
66219  *
66220  * <p><u>Loading Nested Data</u></p>
66221  *
66222  * <p>Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
66223  * 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
66224  * and allow the Reader to automatically populate the associated models. Below is a brief example, see the {@link Ext.data.reader.Reader} intro
66225  * docs for a full explanation:</p>
66226  *
66227 <pre><code>
66228 var store = Ext.create('Ext.data.Store', {
66229     autoLoad: true,
66230     model: "User",
66231     proxy: {
66232         type: 'ajax',
66233         url : 'users.json',
66234         reader: {
66235             type: 'json',
66236             root: 'users'
66237         }
66238     }
66239 });
66240 </code></pre>
66241  *
66242  * <p>Which would consume a response like this:</p>
66243  *
66244 <pre><code>
66245 {
66246     "users": [
66247         {
66248             "id": 1,
66249             "name": "Ed",
66250             "orders": [
66251                 {
66252                     "id": 10,
66253                     "total": 10.76,
66254                     "status": "invoiced"
66255                 },
66256                 {
66257                     "id": 11,
66258                     "total": 13.45,
66259                     "status": "shipped"
66260                 }
66261             ]
66262         }
66263     ]
66264 }
66265 </code></pre>
66266  *
66267  * <p>See the {@link Ext.data.reader.Reader} intro docs for a full explanation.</p>
66268  *
66269  * <p><u>Filtering and Sorting</u></p>
66270  *
66271  * <p>Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and {@link #filters} are
66272  * held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage. Usually it is sufficient to
66273  * either just specify sorters and filters in the Store configuration or call {@link #sort} or {@link #filter}:
66274  *
66275 <pre><code>
66276 var store = Ext.create('Ext.data.Store', {
66277     model: 'User',
66278     sorters: [
66279         {
66280             property : 'age',
66281             direction: 'DESC'
66282         },
66283         {
66284             property : 'firstName',
66285             direction: 'ASC'
66286         }
66287     ],
66288
66289     filters: [
66290         {
66291             property: 'firstName',
66292             value   : /Ed/
66293         }
66294     ]
66295 });
66296 </code></pre>
66297  *
66298  * <p>The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By default, sorting
66299  * and filtering are both performed locally by the Store - see {@link #remoteSort} and {@link #remoteFilter} to allow the server to
66300  * perform these operations instead.</p>
66301  *
66302  * <p>Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter to the Store
66303  * and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all existing filters). Note that by
66304  * default {@link #sortOnFilter} is set to true, which means that your sorters are automatically reapplied if using local sorting.</p>
66305  *
66306 <pre><code>
66307 store.filter('eyeColor', 'Brown');
66308 </code></pre>
66309  *
66310  * <p>Change the sorting at any time by calling {@link #sort}:</p>
66311  *
66312 <pre><code>
66313 store.sort('height', 'ASC');
66314 </code></pre>
66315  *
66316  * <p>Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no arguments,
66317  * the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new ones, just add them
66318  * to the MixedCollection:</p>
66319  *
66320 <pre><code>
66321 store.sorters.add(new Ext.util.Sorter({
66322     property : 'shoeSize',
66323     direction: 'ASC'
66324 }));
66325
66326 store.sort();
66327 </code></pre>
66328  *
66329  * <p><u>Registering with StoreManager</u></p>
66330  *
66331  * <p>Any Store that is instantiated with a {@link #storeId} will automatically be registed with the {@link Ext.data.StoreManager StoreManager}.
66332  * This makes it easy to reuse the same store in multiple views:</p>
66333  *
66334  <pre><code>
66335 //this store can be used several times
66336 Ext.create('Ext.data.Store', {
66337     model: 'User',
66338     storeId: 'usersStore'
66339 });
66340
66341 new Ext.List({
66342     store: 'usersStore',
66343
66344     //other config goes here
66345 });
66346
66347 new Ext.view.View({
66348     store: 'usersStore',
66349
66350     //other config goes here
66351 });
66352 </code></pre>
66353  *
66354  * <p><u>Further Reading</u></p>
66355  *
66356  * <p>Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
66357  * pieces and how they fit together, see:</p>
66358  *
66359  * <ul style="list-style-type: disc; padding-left: 25px">
66360  * <li>{@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used</li>
66361  * <li>{@link Ext.data.Model Model} - the core class in the data package</li>
66362  * <li>{@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response</li>
66363  * </ul>
66364  *
66365  */
66366 Ext.define('Ext.data.Store', {
66367     extend: 'Ext.data.AbstractStore',
66368
66369     alias: 'store.store',
66370
66371     requires: ['Ext.data.StoreManager', 'Ext.ModelManager', 'Ext.data.Model', 'Ext.util.Grouper'],
66372     uses: ['Ext.data.proxy.Memory'],
66373
66374     /**
66375      * @cfg {Boolean} remoteSort
66376      * True to defer any sorting operation to the server. If false, sorting is done locally on the client. Defaults to <tt>false</tt>.
66377      */
66378     remoteSort: false,
66379
66380     /**
66381      * @cfg {Boolean} remoteFilter
66382      * True to defer any filtering operation to the server. If false, filtering is done locally on the client. Defaults to <tt>false</tt>.
66383      */
66384     remoteFilter: false,
66385
66386     /**
66387      * @cfg {Boolean} remoteGroup
66388      * True if the grouping should apply on the server side, false if it is local only.  If the
66389      * grouping is local, it can be applied immediately to the data.  If it is remote, then it will simply act as a
66390      * helper, automatically sending the grouping information to the server.
66391      */
66392     remoteGroup : false,
66393
66394     /**
66395      * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
66396      * object or a Proxy instance - see {@link #setProxy} for details.
66397      */
66398
66399     /**
66400      * @cfg {Object[]/Ext.data.Model[]} data Optional array of Model instances or data objects to load locally. See "Inline data" above for details.
66401      */
66402
66403     /**
66404      * @property {String} groupField
66405      * The field by which to group data in the store. Internally, grouping is very similar to sorting - the
66406      * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
66407      * level of grouping, and groups can be fetched via the {@link #getGroups} method.
66408      */
66409     groupField: undefined,
66410
66411     /**
66412      * The direction in which sorting should be applied when grouping. Defaults to "ASC" - the other supported value is "DESC"
66413      * @property groupDir
66414      * @type String
66415      */
66416     groupDir: "ASC",
66417
66418     /**
66419      * @cfg {Number} pageSize
66420      * The number of records considered to form a 'page'. This is used to power the built-in
66421      * paging using the nextPage and previousPage functions. Defaults to 25.
66422      */
66423     pageSize: 25,
66424
66425     /**
66426      * The page that the Store has most recently loaded (see {@link #loadPage})
66427      * @property currentPage
66428      * @type Number
66429      */
66430     currentPage: 1,
66431
66432     /**
66433      * @cfg {Boolean} clearOnPageLoad True to empty the store when loading another page via {@link #loadPage},
66434      * {@link #nextPage} or {@link #previousPage}. Setting to false keeps existing records, allowing
66435      * large data sets to be loaded one page at a time but rendered all together.
66436      */
66437     clearOnPageLoad: true,
66438
66439     /**
66440      * @property {Boolean} loading
66441      * True if the Store is currently loading via its Proxy
66442      * @private
66443      */
66444     loading: false,
66445
66446     /**
66447      * @cfg {Boolean} sortOnFilter For local filtering only, causes {@link #sort} to be called whenever {@link #filter} is called,
66448      * causing the sorters to be reapplied after filtering. Defaults to true
66449      */
66450     sortOnFilter: true,
66451
66452     /**
66453      * @cfg {Boolean} buffered
66454      * Allow the store to buffer and pre-fetch pages of records. This is to be used in conjunction with a view will
66455      * tell the store to pre-fetch records ahead of a time.
66456      */
66457     buffered: false,
66458
66459     /**
66460      * @cfg {Number} purgePageCount
66461      * The number of pages to keep in the cache before purging additional records. A value of 0 indicates to never purge the prefetched data.
66462      * This option is only relevant when the {@link #buffered} option is set to true.
66463      */
66464     purgePageCount: 5,
66465
66466     isStore: true,
66467
66468     onClassExtended: function(cls, data) {
66469         var model = data.model;
66470
66471         if (typeof model == 'string') {
66472             var onBeforeClassCreated = data.onBeforeClassCreated;
66473
66474             data.onBeforeClassCreated = function(cls, data) {
66475                 var me = this;
66476
66477                 Ext.require(model, function() {
66478                     onBeforeClassCreated.call(me, cls, data);
66479                 });
66480             };
66481         }
66482     },
66483
66484     /**
66485      * Creates the store.
66486      * @param {Object} config (optional) Config object
66487      */
66488     constructor: function(config) {
66489         // Clone the config so we don't modify the original config object
66490         config = Ext.Object.merge({}, config);
66491
66492         var me = this,
66493             groupers = config.groupers || me.groupers,
66494             groupField = config.groupField || me.groupField,
66495             proxy,
66496             data;
66497
66498         if (config.buffered || me.buffered) {
66499             me.prefetchData = Ext.create('Ext.util.MixedCollection', false, function(record) {
66500                 return record.index;
66501             });
66502             me.pendingRequests = [];
66503             me.pagesRequested = [];
66504
66505             me.sortOnLoad = false;
66506             me.filterOnLoad = false;
66507         }
66508
66509         me.addEvents(
66510             /**
66511              * @event beforeprefetch
66512              * Fires before a prefetch occurs. Return false to cancel.
66513              * @param {Ext.data.Store} this
66514              * @param {Ext.data.Operation} operation The associated operation
66515              */
66516             'beforeprefetch',
66517             /**
66518              * @event groupchange
66519              * Fired whenever the grouping in the grid changes
66520              * @param {Ext.data.Store} store The store
66521              * @param {Ext.util.Grouper[]} groupers The array of grouper objects
66522              */
66523             'groupchange',
66524             /**
66525              * @event load
66526              * Fires whenever records have been prefetched
66527              * @param {Ext.data.Store} this
66528              * @param {Ext.util.Grouper[]} records An array of records
66529              * @param {Boolean} successful True if the operation was successful.
66530              * @param {Ext.data.Operation} operation The associated operation
66531              */
66532             'prefetch'
66533         );
66534         data = config.data || me.data;
66535
66536         /**
66537          * The MixedCollection that holds this store's local cache of records
66538          * @property data
66539          * @type Ext.util.MixedCollection
66540          */
66541         me.data = Ext.create('Ext.util.MixedCollection', false, function(record) {
66542             return record.internalId;
66543         });
66544
66545         if (data) {
66546             me.inlineData = data;
66547             delete config.data;
66548         }
66549
66550         if (!groupers && groupField) {
66551             groupers = [{
66552                 property : groupField,
66553                 direction: config.groupDir || me.groupDir
66554             }];
66555         }
66556         delete config.groupers;
66557
66558         /**
66559          * The collection of {@link Ext.util.Grouper Groupers} currently applied to this Store
66560          * @property groupers
66561          * @type Ext.util.MixedCollection
66562          */
66563         me.groupers = Ext.create('Ext.util.MixedCollection');
66564         me.groupers.addAll(me.decodeGroupers(groupers));
66565
66566         this.callParent([config]);
66567         // don't use *config* anymore from here on... use *me* instead...
66568
66569         if (me.groupers.items.length) {
66570             me.sort(me.groupers.items, 'prepend', false);
66571         }
66572
66573         proxy = me.proxy;
66574         data = me.inlineData;
66575
66576         if (data) {
66577             if (proxy instanceof Ext.data.proxy.Memory) {
66578                 proxy.data = data;
66579                 me.read();
66580             } else {
66581                 me.add.apply(me, data);
66582             }
66583
66584             me.sort();
66585             delete me.inlineData;
66586         } else if (me.autoLoad) {
66587             Ext.defer(me.load, 10, me, [typeof me.autoLoad === 'object' ? me.autoLoad: undefined]);
66588             // Remove the defer call, we may need reinstate this at some point, but currently it's not obvious why it's here.
66589             // this.load(typeof this.autoLoad == 'object' ? this.autoLoad : undefined);
66590         }
66591     },
66592
66593     onBeforeSort: function() {
66594         var groupers = this.groupers;
66595         if (groupers.getCount() > 0) {
66596             this.sort(groupers.items, 'prepend', false);
66597         }
66598     },
66599
66600     /**
66601      * @private
66602      * Normalizes an array of grouper objects, ensuring that they are all Ext.util.Grouper instances
66603      * @param {Object[]} groupers The groupers array
66604      * @return {Ext.util.Grouper[]} Array of Ext.util.Grouper objects
66605      */
66606     decodeGroupers: function(groupers) {
66607         if (!Ext.isArray(groupers)) {
66608             if (groupers === undefined) {
66609                 groupers = [];
66610             } else {
66611                 groupers = [groupers];
66612             }
66613         }
66614
66615         var length  = groupers.length,
66616             Grouper = Ext.util.Grouper,
66617             config, i;
66618
66619         for (i = 0; i < length; i++) {
66620             config = groupers[i];
66621
66622             if (!(config instanceof Grouper)) {
66623                 if (Ext.isString(config)) {
66624                     config = {
66625                         property: config
66626                     };
66627                 }
66628
66629                 Ext.applyIf(config, {
66630                     root     : 'data',
66631                     direction: "ASC"
66632                 });
66633
66634                 //support for 3.x style sorters where a function can be defined as 'fn'
66635                 if (config.fn) {
66636                     config.sorterFn = config.fn;
66637                 }
66638
66639                 //support a function to be passed as a sorter definition
66640                 if (typeof config == 'function') {
66641                     config = {
66642                         sorterFn: config
66643                     };
66644                 }
66645
66646                 groupers[i] = new Grouper(config);
66647             }
66648         }
66649
66650         return groupers;
66651     },
66652
66653     /**
66654      * Group data in the store
66655      * @param {String/Object[]} groupers Either a string name of one of the fields in this Store's configured {@link Ext.data.Model Model},
66656      * or an Array of grouper configurations.
66657      * @param {String} direction The overall direction to group the data by. Defaults to "ASC".
66658      */
66659     group: function(groupers, direction) {
66660         var me = this,
66661             hasNew = false,
66662             grouper,
66663             newGroupers;
66664
66665         if (Ext.isArray(groupers)) {
66666             newGroupers = groupers;
66667         } else if (Ext.isObject(groupers)) {
66668             newGroupers = [groupers];
66669         } else if (Ext.isString(groupers)) {
66670             grouper = me.groupers.get(groupers);
66671
66672             if (!grouper) {
66673                 grouper = {
66674                     property : groupers,
66675                     direction: direction
66676                 };
66677                 newGroupers = [grouper];
66678             } else if (direction === undefined) {
66679                 grouper.toggle();
66680             } else {
66681                 grouper.setDirection(direction);
66682             }
66683         }
66684
66685         if (newGroupers && newGroupers.length) {
66686             hasNew = true;
66687             newGroupers = me.decodeGroupers(newGroupers);
66688             me.groupers.clear();
66689             me.groupers.addAll(newGroupers);
66690         }
66691
66692         if (me.remoteGroup) {
66693             me.load({
66694                 scope: me,
66695                 callback: me.fireGroupChange
66696             });
66697         } else {
66698             // need to explicitly force a sort if we have groupers
66699             me.sort(null, null, null, hasNew);
66700             me.fireGroupChange();
66701         }
66702     },
66703
66704     /**
66705      * Clear any groupers in the store
66706      */
66707     clearGrouping: function(){
66708         var me = this;
66709         // Clear any groupers we pushed on to the sorters
66710         me.groupers.each(function(grouper){
66711             me.sorters.remove(grouper);
66712         });
66713         me.groupers.clear();
66714         if (me.remoteGroup) {
66715             me.load({
66716                 scope: me,
66717                 callback: me.fireGroupChange
66718             });
66719         } else {
66720             me.sort();
66721             me.fireEvent('groupchange', me, me.groupers);
66722         }
66723     },
66724
66725     /**
66726      * Checks if the store is currently grouped
66727      * @return {Boolean} True if the store is grouped.
66728      */
66729     isGrouped: function() {
66730         return this.groupers.getCount() > 0;
66731     },
66732
66733     /**
66734      * Fires the groupchange event. Abstracted out so we can use it
66735      * as a callback
66736      * @private
66737      */
66738     fireGroupChange: function(){
66739         this.fireEvent('groupchange', this, this.groupers);
66740     },
66741
66742     /**
66743      * Returns an array containing the result of applying grouping to the records in this store. See {@link #groupField},
66744      * {@link #groupDir} and {@link #getGroupString}. Example for a store containing records with a color field:
66745 <pre><code>
66746 var myStore = Ext.create('Ext.data.Store', {
66747     groupField: 'color',
66748     groupDir  : 'DESC'
66749 });
66750
66751 myStore.getGroups(); //returns:
66752 [
66753     {
66754         name: 'yellow',
66755         children: [
66756             //all records where the color field is 'yellow'
66757         ]
66758     },
66759     {
66760         name: 'red',
66761         children: [
66762             //all records where the color field is 'red'
66763         ]
66764     }
66765 ]
66766 </code></pre>
66767      * @param {String} groupName (Optional) Pass in an optional groupName argument to access a specific group as defined by {@link #getGroupString}
66768      * @return {Object/Object[]} The grouped data
66769      */
66770     getGroups: function(requestGroupString) {
66771         var records = this.data.items,
66772             length = records.length,
66773             groups = [],
66774             pointers = {},
66775             record,
66776             groupStr,
66777             group,
66778             i;
66779
66780         for (i = 0; i < length; i++) {
66781             record = records[i];
66782             groupStr = this.getGroupString(record);
66783             group = pointers[groupStr];
66784
66785             if (group === undefined) {
66786                 group = {
66787                     name: groupStr,
66788                     children: []
66789                 };
66790
66791                 groups.push(group);
66792                 pointers[groupStr] = group;
66793             }
66794
66795             group.children.push(record);
66796         }
66797
66798         return requestGroupString ? pointers[requestGroupString] : groups;
66799     },
66800
66801     /**
66802      * @private
66803      * For a given set of records and a Grouper, returns an array of arrays - each of which is the set of records
66804      * matching a certain group.
66805      */
66806     getGroupsForGrouper: function(records, grouper) {
66807         var length = records.length,
66808             groups = [],
66809             oldValue,
66810             newValue,
66811             record,
66812             group,
66813             i;
66814
66815         for (i = 0; i < length; i++) {
66816             record = records[i];
66817             newValue = grouper.getGroupString(record);
66818
66819             if (newValue !== oldValue) {
66820                 group = {
66821                     name: newValue,
66822                     grouper: grouper,
66823                     records: []
66824                 };
66825                 groups.push(group);
66826             }
66827
66828             group.records.push(record);
66829
66830             oldValue = newValue;
66831         }
66832
66833         return groups;
66834     },
66835
66836     /**
66837      * @private
66838      * This is used recursively to gather the records into the configured Groupers. The data MUST have been sorted for
66839      * this to work properly (see {@link #getGroupData} and {@link #getGroupsForGrouper}) Most of the work is done by
66840      * {@link #getGroupsForGrouper} - this function largely just handles the recursion.
66841      * @param {Ext.data.Model[]} records The set or subset of records to group
66842      * @param {Number} grouperIndex The grouper index to retrieve
66843      * @return {Object[]} The grouped records
66844      */
66845     getGroupsForGrouperIndex: function(records, grouperIndex) {
66846         var me = this,
66847             groupers = me.groupers,
66848             grouper = groupers.getAt(grouperIndex),
66849             groups = me.getGroupsForGrouper(records, grouper),
66850             length = groups.length,
66851             i;
66852
66853         if (grouperIndex + 1 < groupers.length) {
66854             for (i = 0; i < length; i++) {
66855                 groups[i].children = me.getGroupsForGrouperIndex(groups[i].records, grouperIndex + 1);
66856             }
66857         }
66858
66859         for (i = 0; i < length; i++) {
66860             groups[i].depth = grouperIndex;
66861         }
66862
66863         return groups;
66864     },
66865
66866     /**
66867      * @private
66868      * <p>Returns records grouped by the configured {@link #groupers grouper} configuration. Sample return value (in
66869      * this case grouping by genre and then author in a fictional books dataset):</p>
66870 <pre><code>
66871 [
66872     {
66873         name: 'Fantasy',
66874         depth: 0,
66875         records: [
66876             //book1, book2, book3, book4
66877         ],
66878         children: [
66879             {
66880                 name: 'Rowling',
66881                 depth: 1,
66882                 records: [
66883                     //book1, book2
66884                 ]
66885             },
66886             {
66887                 name: 'Tolkein',
66888                 depth: 1,
66889                 records: [
66890                     //book3, book4
66891                 ]
66892             }
66893         ]
66894     }
66895 ]
66896 </code></pre>
66897      * @param {Boolean} sort True to call {@link #sort} before finding groups. Sorting is required to make grouping
66898      * function correctly so this should only be set to false if the Store is known to already be sorted correctly
66899      * (defaults to true)
66900      * @return {Object[]} The group data
66901      */
66902     getGroupData: function(sort) {
66903         var me = this;
66904         if (sort !== false) {
66905             me.sort();
66906         }
66907
66908         return me.getGroupsForGrouperIndex(me.data.items, 0);
66909     },
66910
66911     /**
66912      * <p>Returns the string to group on for a given model instance. The default implementation of this method returns
66913      * the model's {@link #groupField}, but this can be overridden to group by an arbitrary string. For example, to
66914      * group by the first letter of a model's 'name' field, use the following code:</p>
66915 <pre><code>
66916 Ext.create('Ext.data.Store', {
66917     groupDir: 'ASC',
66918     getGroupString: function(instance) {
66919         return instance.get('name')[0];
66920     }
66921 });
66922 </code></pre>
66923      * @param {Ext.data.Model} instance The model instance
66924      * @return {String} The string to compare when forming groups
66925      */
66926     getGroupString: function(instance) {
66927         var group = this.groupers.first();
66928         if (group) {
66929             return instance.get(group.property);
66930         }
66931         return '';
66932     },
66933     /**
66934      * Inserts Model instances into the Store at the given index and fires the {@link #add} event.
66935      * See also <code>{@link #add}</code>.
66936      * @param {Number} index The start index at which to insert the passed Records.
66937      * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the cache.
66938      */
66939     insert: function(index, records) {
66940         var me = this,
66941             sync = false,
66942             i,
66943             record,
66944             len;
66945
66946         records = [].concat(records);
66947         for (i = 0, len = records.length; i < len; i++) {
66948             record = me.createModel(records[i]);
66949             record.set(me.modelDefaults);
66950             // reassign the model in the array in case it wasn't created yet
66951             records[i] = record;
66952
66953             me.data.insert(index + i, record);
66954             record.join(me);
66955
66956             sync = sync || record.phantom === true;
66957         }
66958
66959         if (me.snapshot) {
66960             me.snapshot.addAll(records);
66961         }
66962
66963         me.fireEvent('add', me, records, index);
66964         me.fireEvent('datachanged', me);
66965         if (me.autoSync && sync) {
66966             me.sync();
66967         }
66968     },
66969
66970     /**
66971      * Adds Model instance to the Store. This method accepts either:
66972      *
66973      * - An array of Model instances or Model configuration objects.
66974      * - Any number of Model instance or Model configuration object arguments.
66975      *
66976      * The new Model instances will be added at the end of the existing collection.
66977      *
66978      * Sample usage:
66979      *
66980      *     myStore.add({some: 'data'}, {some: 'other data'});
66981      *
66982      * @param {Ext.data.Model[]/Ext.data.Model...} model An array of Model instances
66983      * or Model configuration objects, or variable number of Model instance or config arguments.
66984      * @return {Ext.data.Model[]} The model instances that were added
66985      */
66986     add: function(records) {
66987         //accept both a single-argument array of records, or any number of record arguments
66988         if (!Ext.isArray(records)) {
66989             records = Array.prototype.slice.apply(arguments);
66990         }
66991
66992         var me = this,
66993             i = 0,
66994             length = records.length,
66995             record;
66996
66997         for (; i < length; i++) {
66998             record = me.createModel(records[i]);
66999             // reassign the model in the array in case it wasn't created yet
67000             records[i] = record;
67001         }
67002
67003         me.insert(me.data.length, records);
67004
67005         return records;
67006     },
67007
67008     /**
67009      * Converts a literal to a model, if it's not a model already
67010      * @private
67011      * @param record {Ext.data.Model/Object} The record to create
67012      * @return {Ext.data.Model}
67013      */
67014     createModel: function(record) {
67015         if (!record.isModel) {
67016             record = Ext.ModelManager.create(record, this.model);
67017         }
67018
67019         return record;
67020     },
67021
67022     /**
67023      * Calls the specified function for each of the {@link Ext.data.Model Records} in the cache.
67024      * @param {Function} fn The function to call. The {@link Ext.data.Model Record} is passed as the first parameter.
67025      * Returning <tt>false</tt> aborts and exits the iteration.
67026      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed.
67027      * Defaults to the current {@link Ext.data.Model Record} in the iteration.
67028      */
67029     each: function(fn, scope) {
67030         this.data.each(fn, scope);
67031     },
67032
67033     /**
67034      * Removes the given record from the Store, firing the 'remove' event for each instance that is removed, plus a single
67035      * 'datachanged' event after removal.
67036      * @param {Ext.data.Model/Ext.data.Model[]} records The Ext.data.Model instance or array of instances to remove
67037      */
67038     remove: function(records, /* private */ isMove) {
67039         if (!Ext.isArray(records)) {
67040             records = [records];
67041         }
67042
67043         /*
67044          * Pass the isMove parameter if we know we're going to be re-inserting this record
67045          */
67046         isMove = isMove === true;
67047         var me = this,
67048             sync = false,
67049             i = 0,
67050             length = records.length,
67051             isPhantom,
67052             index,
67053             record;
67054
67055         for (; i < length; i++) {
67056             record = records[i];
67057             index = me.data.indexOf(record);
67058
67059             if (me.snapshot) {
67060                 me.snapshot.remove(record);
67061             }
67062
67063             if (index > -1) {
67064                 isPhantom = record.phantom === true;
67065                 if (!isMove && !isPhantom) {
67066                     // don't push phantom records onto removed
67067                     me.removed.push(record);
67068                 }
67069
67070                 record.unjoin(me);
67071                 me.data.remove(record);
67072                 sync = sync || !isPhantom;
67073
67074                 me.fireEvent('remove', me, record, index);
67075             }
67076         }
67077
67078         me.fireEvent('datachanged', me);
67079         if (!isMove && me.autoSync && sync) {
67080             me.sync();
67081         }
67082     },
67083
67084     /**
67085      * Removes the model instance at the given index
67086      * @param {Number} index The record index
67087      */
67088     removeAt: function(index) {
67089         var record = this.getAt(index);
67090
67091         if (record) {
67092             this.remove(record);
67093         }
67094     },
67095
67096     /**
67097      * <p>Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
67098      * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
67099      * instances into the Store and calling an optional callback if required. Example usage:</p>
67100      *
67101 <pre><code>
67102 store.load({
67103     scope   : this,
67104     callback: function(records, operation, success) {
67105         //the {@link Ext.data.Operation operation} object contains all of the details of the load operation
67106         console.log(records);
67107     }
67108 });
67109 </code></pre>
67110      *
67111      * <p>If the callback scope does not need to be set, a function can simply be passed:</p>
67112      *
67113 <pre><code>
67114 store.load(function(records, operation, success) {
67115     console.log('loaded records');
67116 });
67117 </code></pre>
67118      *
67119      * @param {Object/Function} options (Optional) config object, passed into the Ext.data.Operation object before loading.
67120      */
67121     load: function(options) {
67122         var me = this;
67123
67124         options = options || {};
67125
67126         if (Ext.isFunction(options)) {
67127             options = {
67128                 callback: options
67129             };
67130         }
67131
67132         Ext.applyIf(options, {
67133             groupers: me.groupers.items,
67134             page: me.currentPage,
67135             start: (me.currentPage - 1) * me.pageSize,
67136             limit: me.pageSize,
67137             addRecords: false
67138         });
67139
67140         return me.callParent([options]);
67141     },
67142
67143     /**
67144      * @private
67145      * Called internally when a Proxy has completed a load request
67146      */
67147     onProxyLoad: function(operation) {
67148         var me = this,
67149             resultSet = operation.getResultSet(),
67150             records = operation.getRecords(),
67151             successful = operation.wasSuccessful();
67152
67153         if (resultSet) {
67154             me.totalCount = resultSet.total;
67155         }
67156
67157         if (successful) {
67158             me.loadRecords(records, operation);
67159         }
67160
67161         me.loading = false;
67162         me.fireEvent('load', me, records, successful);
67163
67164         //TODO: deprecate this event, it should always have been 'load' instead. 'load' is now documented, 'read' is not.
67165         //People are definitely using this so can't deprecate safely until 2.x
67166         me.fireEvent('read', me, records, operation.wasSuccessful());
67167
67168         //this is a callback that would have been passed to the 'read' function and is optional
67169         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
67170     },
67171
67172     /**
67173      * Create any new records when a write is returned from the server.
67174      * @private
67175      * @param {Ext.data.Model[]} records The array of new records
67176      * @param {Ext.data.Operation} operation The operation that just completed
67177      * @param {Boolean} success True if the operation was successful
67178      */
67179     onCreateRecords: function(records, operation, success) {
67180         if (success) {
67181             var i = 0,
67182                 data = this.data,
67183                 snapshot = this.snapshot,
67184                 length = records.length,
67185                 originalRecords = operation.records,
67186                 record,
67187                 original,
67188                 index;
67189
67190             /*
67191              * Loop over each record returned from the server. Assume they are
67192              * returned in order of how they were sent. If we find a matching
67193              * record, replace it with the newly created one.
67194              */
67195             for (; i < length; ++i) {
67196                 record = records[i];
67197                 original = originalRecords[i];
67198                 if (original) {
67199                     index = data.indexOf(original);
67200                     if (index > -1) {
67201                         data.removeAt(index);
67202                         data.insert(index, record);
67203                     }
67204                     if (snapshot) {
67205                         index = snapshot.indexOf(original);
67206                         if (index > -1) {
67207                             snapshot.removeAt(index);
67208                             snapshot.insert(index, record);
67209                         }
67210                     }
67211                     record.phantom = false;
67212                     record.join(this);
67213                 }
67214             }
67215         }
67216     },
67217
67218     /**
67219      * Update any records when a write is returned from the server.
67220      * @private
67221      * @param {Ext.data.Model[]} records The array of updated records
67222      * @param {Ext.data.Operation} operation The operation that just completed
67223      * @param {Boolean} success True if the operation was successful
67224      */
67225     onUpdateRecords: function(records, operation, success){
67226         if (success) {
67227             var i = 0,
67228                 length = records.length,
67229                 data = this.data,
67230                 snapshot = this.snapshot,
67231                 record;
67232
67233             for (; i < length; ++i) {
67234                 record = records[i];
67235                 data.replace(record);
67236                 if (snapshot) {
67237                     snapshot.replace(record);
67238                 }
67239                 record.join(this);
67240             }
67241         }
67242     },
67243
67244     /**
67245      * Remove any records when a write is returned from the server.
67246      * @private
67247      * @param {Ext.data.Model[]} records The array of removed records
67248      * @param {Ext.data.Operation} operation The operation that just completed
67249      * @param {Boolean} success True if the operation was successful
67250      */
67251     onDestroyRecords: function(records, operation, success){
67252         if (success) {
67253             var me = this,
67254                 i = 0,
67255                 length = records.length,
67256                 data = me.data,
67257                 snapshot = me.snapshot,
67258                 record;
67259
67260             for (; i < length; ++i) {
67261                 record = records[i];
67262                 record.unjoin(me);
67263                 data.remove(record);
67264                 if (snapshot) {
67265                     snapshot.remove(record);
67266                 }
67267             }
67268             me.removed = [];
67269         }
67270     },
67271
67272     //inherit docs
67273     getNewRecords: function() {
67274         return this.data.filterBy(this.filterNew).items;
67275     },
67276
67277     //inherit docs
67278     getUpdatedRecords: function() {
67279         return this.data.filterBy(this.filterUpdated).items;
67280     },
67281
67282     /**
67283      * Filters the loaded set of records by a given set of filters.
67284      *
67285      * Filtering by single field:
67286      *
67287      *     store.filter("email", /\.com$/);
67288      *
67289      * Using multiple filters:
67290      *
67291      *     store.filter([
67292      *         {property: "email", value: /\.com$/},
67293      *         {filterFn: function(item) { return item.get("age") > 10; }}
67294      *     ]);
67295      *
67296      * Using Ext.util.Filter instances instead of config objects
67297      * (note that we need to specify the {@link Ext.util.Filter#root root} config option in this case):
67298      *
67299      *     store.filter([
67300      *         Ext.create('Ext.util.Filter', {property: "email", value: /\.com$/, root: 'data'}),
67301      *         Ext.create('Ext.util.Filter', {filterFn: function(item) { return item.get("age") > 10; }, root: 'data'})
67302      *     ]);
67303      *
67304      * @param {Object[]/Ext.util.Filter[]/String} filters The set of filters to apply to the data. These are stored internally on the store,
67305      * but the filtering itself is done on the Store's {@link Ext.util.MixedCollection MixedCollection}. See
67306      * MixedCollection's {@link Ext.util.MixedCollection#filter filter} method for filter syntax. Alternatively,
67307      * pass in a property string
67308      * @param {String} value (optional) value to filter by (only if using a property string as the first argument)
67309      */
67310     filter: function(filters, value) {
67311         if (Ext.isString(filters)) {
67312             filters = {
67313                 property: filters,
67314                 value: value
67315             };
67316         }
67317
67318         var me = this,
67319             decoded = me.decodeFilters(filters),
67320             i = 0,
67321             doLocalSort = me.sortOnFilter && !me.remoteSort,
67322             length = decoded.length;
67323
67324         for (; i < length; i++) {
67325             me.filters.replace(decoded[i]);
67326         }
67327
67328         if (me.remoteFilter) {
67329             //the load function will pick up the new filters and request the filtered data from the proxy
67330             me.load();
67331         } else {
67332             /**
67333              * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
67334              * records when a filter is removed or changed
67335              * @property snapshot
67336              * @type Ext.util.MixedCollection
67337              */
67338             if (me.filters.getCount()) {
67339                 me.snapshot = me.snapshot || me.data.clone();
67340                 me.data = me.data.filter(me.filters.items);
67341
67342                 if (doLocalSort) {
67343                     me.sort();
67344                 }
67345                 // fire datachanged event if it hasn't already been fired by doSort
67346                 if (!doLocalSort || me.sorters.length < 1) {
67347                     me.fireEvent('datachanged', me);
67348                 }
67349             }
67350         }
67351     },
67352
67353     /**
67354      * Revert to a view of the Record cache with no filtering applied.
67355      * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
67356      * {@link #datachanged} event.
67357      */
67358     clearFilter: function(suppressEvent) {
67359         var me = this;
67360
67361         me.filters.clear();
67362
67363         if (me.remoteFilter) {
67364             me.load();
67365         } else if (me.isFiltered()) {
67366             me.data = me.snapshot.clone();
67367             delete me.snapshot;
67368
67369             if (suppressEvent !== true) {
67370                 me.fireEvent('datachanged', me);
67371             }
67372         }
67373     },
67374
67375     /**
67376      * Returns true if this store is currently filtered
67377      * @return {Boolean}
67378      */
67379     isFiltered: function() {
67380         var snapshot = this.snapshot;
67381         return !! snapshot && snapshot !== this.data;
67382     },
67383
67384     /**
67385      * Filter by a function. The specified function will be called for each
67386      * Record in this Store. If the function returns <tt>true</tt> the Record is included,
67387      * otherwise it is filtered out.
67388      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
67389      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
67390      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
67391      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
67392      * </ul>
67393      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
67394      */
67395     filterBy: function(fn, scope) {
67396         var me = this;
67397
67398         me.snapshot = me.snapshot || me.data.clone();
67399         me.data = me.queryBy(fn, scope || me);
67400         me.fireEvent('datachanged', me);
67401     },
67402
67403     /**
67404      * Query the cached records in this Store using a filtering function. The specified function
67405      * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
67406      * included in the results.
67407      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
67408      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
67409      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
67410      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
67411      * </ul>
67412      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
67413      * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records
67414      **/
67415     queryBy: function(fn, scope) {
67416         var me = this,
67417         data = me.snapshot || me.data;
67418         return data.filterBy(fn, scope || me);
67419     },
67420
67421     /**
67422      * Loads an array of data straight into the Store.
67423      * 
67424      * Using this method is great if the data is in the correct format already (e.g. it doesn't need to be
67425      * processed by a reader). If your data requires processing to decode the data structure, use a
67426      * {@link Ext.data.proxy.Memory MemoryProxy} instead.
67427      * 
67428      * @param {Ext.data.Model[]/Object[]} data Array of data to load. Any non-model instances will be cast
67429      * into model instances.
67430      * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
67431      * to remove the old ones first.
67432      */
67433     loadData: function(data, append) {
67434         var model = this.model,
67435             length = data.length,
67436             newData = [],
67437             i,
67438             record;
67439
67440         //make sure each data element is an Ext.data.Model instance
67441         for (i = 0; i < length; i++) {
67442             record = data[i];
67443
67444             if (!(record instanceof Ext.data.Model)) {
67445                 record = Ext.ModelManager.create(record, model);
67446             }
67447             newData.push(record);
67448         }
67449
67450         this.loadRecords(newData, {addRecords: append});
67451     },
67452
67453
67454     /**
67455      * Loads data via the bound Proxy's reader
67456      *
67457      * Use this method if you are attempting to load data and want to utilize the configured data reader.
67458      *
67459      * @param {Object[]} data The full JSON object you'd like to load into the Data store.
67460      * @param {Boolean} [append=false] True to add the records to the existing records in the store, false
67461      * to remove the old ones first.
67462      */
67463     loadRawData : function(data, append) {
67464          var me      = this,
67465              result  = me.proxy.reader.read(data),
67466              records = result.records;
67467
67468          if (result.success) {
67469              me.loadRecords(records, { addRecords: append });
67470              me.fireEvent('load', me, records, true);
67471          }
67472      },
67473
67474
67475     /**
67476      * Loads an array of {@link Ext.data.Model model} instances into the store, fires the datachanged event. This should only usually
67477      * be called internally when loading from the {@link Ext.data.proxy.Proxy Proxy}, when adding records manually use {@link #add} instead
67478      * @param {Ext.data.Model[]} records The array of records to load
67479      * @param {Object} options {addRecords: true} to add these records to the existing records, false to remove the Store's existing records first
67480      */
67481     loadRecords: function(records, options) {
67482         var me     = this,
67483             i      = 0,
67484             length = records.length;
67485
67486         options = options || {};
67487
67488
67489         if (!options.addRecords) {
67490             delete me.snapshot;
67491             me.clearData();
67492         }
67493
67494         me.data.addAll(records);
67495
67496         //FIXME: this is not a good solution. Ed Spencer is totally responsible for this and should be forced to fix it immediately.
67497         for (; i < length; i++) {
67498             if (options.start !== undefined) {
67499                 records[i].index = options.start + i;
67500
67501             }
67502             records[i].join(me);
67503         }
67504
67505         /*
67506          * this rather inelegant suspension and resumption of events is required because both the filter and sort functions
67507          * fire an additional datachanged event, which is not wanted. Ideally we would do this a different way. The first
67508          * datachanged event is fired by the call to this.add, above.
67509          */
67510         me.suspendEvents();
67511
67512         if (me.filterOnLoad && !me.remoteFilter) {
67513             me.filter();
67514         }
67515
67516         if (me.sortOnLoad && !me.remoteSort) {
67517             me.sort();
67518         }
67519
67520         me.resumeEvents();
67521         me.fireEvent('datachanged', me, records);
67522     },
67523
67524     // PAGING METHODS
67525     /**
67526      * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
67527      * load operation, passing in calculated 'start' and 'limit' params
67528      * @param {Number} page The number of the page to load
67529      * @param {Object} options See options for {@link #load}
67530      */
67531     loadPage: function(page, options) {
67532         var me = this;
67533         options = Ext.apply({}, options);
67534
67535         me.currentPage = page;
67536
67537         me.read(Ext.applyIf(options, {
67538             page: page,
67539             start: (page - 1) * me.pageSize,
67540             limit: me.pageSize,
67541             addRecords: !me.clearOnPageLoad
67542         }));
67543     },
67544
67545     /**
67546      * Loads the next 'page' in the current data set
67547      * @param {Object} options See options for {@link #load}
67548      */
67549     nextPage: function(options) {
67550         this.loadPage(this.currentPage + 1, options);
67551     },
67552
67553     /**
67554      * Loads the previous 'page' in the current data set
67555      * @param {Object} options See options for {@link #load}
67556      */
67557     previousPage: function(options) {
67558         this.loadPage(this.currentPage - 1, options);
67559     },
67560
67561     // private
67562     clearData: function() {
67563         var me = this;
67564         me.data.each(function(record) {
67565             record.unjoin(me);
67566         });
67567
67568         me.data.clear();
67569     },
67570
67571     // Buffering
67572     /**
67573      * Prefetches data into the store using its configured {@link #proxy}.
67574      * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
67575      * See {@link #load}
67576      */
67577     prefetch: function(options) {
67578         var me = this,
67579             operation,
67580             requestId = me.getRequestId();
67581
67582         options = options || {};
67583
67584         Ext.applyIf(options, {
67585             action : 'read',
67586             filters: me.filters.items,
67587             sorters: me.sorters.items,
67588             requestId: requestId
67589         });
67590         me.pendingRequests.push(requestId);
67591
67592         operation = Ext.create('Ext.data.Operation', options);
67593
67594         // HACK to implement loadMask support.
67595         //if (operation.blocking) {
67596         //    me.fireEvent('beforeload', me, operation);
67597         //}
67598         if (me.fireEvent('beforeprefetch', me, operation) !== false) {
67599             me.loading = true;
67600             me.proxy.read(operation, me.onProxyPrefetch, me);
67601         }
67602
67603         return me;
67604     },
67605
67606     /**
67607      * Prefetches a page of data.
67608      * @param {Number} page The page to prefetch
67609      * @param {Object} options (Optional) config object, passed into the Ext.data.Operation object before loading.
67610      * See {@link #load}
67611      */
67612     prefetchPage: function(page, options) {
67613         var me = this,
67614             pageSize = me.pageSize,
67615             start = (page - 1) * me.pageSize,
67616             end = start + pageSize;
67617
67618         // Currently not requesting this page and range isn't already satisified
67619         if (Ext.Array.indexOf(me.pagesRequested, page) === -1 && !me.rangeSatisfied(start, end)) {
67620             options = options || {};
67621             me.pagesRequested.push(page);
67622             Ext.applyIf(options, {
67623                 page : page,
67624                 start: start,
67625                 limit: pageSize,
67626                 callback: me.onWaitForGuarantee,
67627                 scope: me
67628             });
67629
67630             me.prefetch(options);
67631         }
67632
67633     },
67634
67635     /**
67636      * Returns a unique requestId to track requests.
67637      * @private
67638      */
67639     getRequestId: function() {
67640         this.requestSeed = this.requestSeed || 1;
67641         return this.requestSeed++;
67642     },
67643
67644     /**
67645      * Called after the configured proxy completes a prefetch operation.
67646      * @private
67647      * @param {Ext.data.Operation} operation The operation that completed
67648      */
67649     onProxyPrefetch: function(operation) {
67650         var me         = this,
67651             resultSet  = operation.getResultSet(),
67652             records    = operation.getRecords(),
67653
67654             successful = operation.wasSuccessful();
67655
67656         if (resultSet) {
67657             me.totalCount = resultSet.total;
67658             me.fireEvent('totalcountchange', me.totalCount);
67659         }
67660
67661         if (successful) {
67662             me.cacheRecords(records, operation);
67663         }
67664         Ext.Array.remove(me.pendingRequests, operation.requestId);
67665         if (operation.page) {
67666             Ext.Array.remove(me.pagesRequested, operation.page);
67667         }
67668
67669         me.loading = false;
67670         me.fireEvent('prefetch', me, records, successful, operation);
67671
67672         // HACK to support loadMask
67673         if (operation.blocking) {
67674             me.fireEvent('load', me, records, successful);
67675         }
67676
67677         //this is a callback that would have been passed to the 'read' function and is optional
67678         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
67679     },
67680
67681     /**
67682      * Caches the records in the prefetch and stripes them with their server-side
67683      * index.
67684      * @private
67685      * @param {Ext.data.Model[]} records The records to cache
67686      * @param {Ext.data.Operation} The associated operation
67687      */
67688     cacheRecords: function(records, operation) {
67689         var me     = this,
67690             i      = 0,
67691             length = records.length,
67692             start  = operation ? operation.start : 0;
67693
67694         if (!Ext.isDefined(me.totalCount)) {
67695             me.totalCount = records.length;
67696             me.fireEvent('totalcountchange', me.totalCount);
67697         }
67698
67699         for (; i < length; i++) {
67700             // this is the true index, not the viewIndex
67701             records[i].index = start + i;
67702         }
67703
67704         me.prefetchData.addAll(records);
67705         if (me.purgePageCount) {
67706             me.purgeRecords();
67707         }
67708
67709     },
67710
67711
67712     /**
67713      * Purge the least recently used records in the prefetch if the purgeCount
67714      * has been exceeded.
67715      */
67716     purgeRecords: function() {
67717         var me = this,
67718             prefetchCount = me.prefetchData.getCount(),
67719             purgeCount = me.purgePageCount * me.pageSize,
67720             numRecordsToPurge = prefetchCount - purgeCount - 1,
67721             i = 0;
67722
67723         for (; i <= numRecordsToPurge; i++) {
67724             me.prefetchData.removeAt(0);
67725         }
67726     },
67727
67728     /**
67729      * Determines if the range has already been satisfied in the prefetchData.
67730      * @private
67731      * @param {Number} start The start index
67732      * @param {Number} end The end index in the range
67733      */
67734     rangeSatisfied: function(start, end) {
67735         var me = this,
67736             i = start,
67737             satisfied = true;
67738
67739         for (; i < end; i++) {
67740             if (!me.prefetchData.getByKey(i)) {
67741                 satisfied = false;
67742                 break;
67743             }
67744         }
67745         return satisfied;
67746     },
67747
67748     /**
67749      * Determines the page from a record index
67750      * @param {Number} index The record index
67751      * @return {Number} The page the record belongs to
67752      */
67753     getPageFromRecordIndex: function(index) {
67754         return Math.floor(index / this.pageSize) + 1;
67755     },
67756
67757     /**
67758      * Handles a guaranteed range being loaded
67759      * @private
67760      */
67761     onGuaranteedRange: function() {
67762         var me = this,
67763             totalCount = me.getTotalCount(),
67764             start = me.requestStart,
67765             end = ((totalCount - 1) < me.requestEnd) ? totalCount - 1 : me.requestEnd,
67766             range = [],
67767             record,
67768             i = start;
67769
67770         end = Math.max(0, end);
67771
67772
67773         if (start !== me.guaranteedStart && end !== me.guaranteedEnd) {
67774             me.guaranteedStart = start;
67775             me.guaranteedEnd = end;
67776
67777             for (; i <= end; i++) {
67778                 record = me.prefetchData.getByKey(i);
67779                 if (record) {
67780                     range.push(record);
67781                 }
67782             }
67783             me.fireEvent('guaranteedrange', range, start, end);
67784             if (me.cb) {
67785                 me.cb.call(me.scope || me, range);
67786             }
67787         }
67788
67789         me.unmask();
67790     },
67791
67792     // hack to support loadmask
67793     mask: function() {
67794         this.masked = true;
67795         this.fireEvent('beforeload');
67796     },
67797
67798     // hack to support loadmask
67799     unmask: function() {
67800         if (this.masked) {
67801             this.fireEvent('load');
67802         }
67803     },
67804
67805     /**
67806      * Returns the number of pending requests out.
67807      */
67808     hasPendingRequests: function() {
67809         return this.pendingRequests.length;
67810     },
67811
67812
67813     // wait until all requests finish, until guaranteeing the range.
67814     onWaitForGuarantee: function() {
67815         if (!this.hasPendingRequests()) {
67816             this.onGuaranteedRange();
67817         }
67818     },
67819
67820     /**
67821      * Guarantee a specific range, this will load the store with a range (that
67822      * must be the pageSize or smaller) and take care of any loading that may
67823      * be necessary.
67824      */
67825     guaranteeRange: function(start, end, cb, scope) {
67826
67827         end = (end > this.totalCount) ? this.totalCount - 1 : end;
67828
67829         var me = this,
67830             i = start,
67831             prefetchData = me.prefetchData,
67832             range = [],
67833             startLoaded = !!prefetchData.getByKey(start),
67834             endLoaded = !!prefetchData.getByKey(end),
67835             startPage = me.getPageFromRecordIndex(start),
67836             endPage = me.getPageFromRecordIndex(end);
67837
67838         me.cb = cb;
67839         me.scope = scope;
67840
67841         me.requestStart = start;
67842         me.requestEnd = end;
67843         // neither beginning or end are loaded
67844         if (!startLoaded || !endLoaded) {
67845             // same page, lets load it
67846             if (startPage === endPage) {
67847                 me.mask();
67848                 me.prefetchPage(startPage, {
67849                     //blocking: true,
67850                     callback: me.onWaitForGuarantee,
67851                     scope: me
67852                 });
67853             // need to load two pages
67854             } else {
67855                 me.mask();
67856                 me.prefetchPage(startPage, {
67857                     //blocking: true,
67858                     callback: me.onWaitForGuarantee,
67859                     scope: me
67860                 });
67861                 me.prefetchPage(endPage, {
67862                     //blocking: true,
67863                     callback: me.onWaitForGuarantee,
67864                     scope: me
67865                 });
67866             }
67867         // Request was already satisfied via the prefetch
67868         } else {
67869             me.onGuaranteedRange();
67870         }
67871     },
67872
67873     // because prefetchData is stored by index
67874     // this invalidates all of the prefetchedData
67875     sort: function() {
67876         var me = this,
67877             prefetchData = me.prefetchData,
67878             sorters,
67879             start,
67880             end,
67881             range;
67882
67883         if (me.buffered) {
67884             if (me.remoteSort) {
67885                 prefetchData.clear();
67886                 me.callParent(arguments);
67887             } else {
67888                 sorters = me.getSorters();
67889                 start = me.guaranteedStart;
67890                 end = me.guaranteedEnd;
67891
67892                 if (sorters.length) {
67893                     prefetchData.sort(sorters);
67894                     range = prefetchData.getRange();
67895                     prefetchData.clear();
67896                     me.cacheRecords(range);
67897                     delete me.guaranteedStart;
67898                     delete me.guaranteedEnd;
67899                     me.guaranteeRange(start, end);
67900                 }
67901                 me.callParent(arguments);
67902             }
67903         } else {
67904             me.callParent(arguments);
67905         }
67906     },
67907
67908     // overriden to provide striping of the indexes as sorting occurs.
67909     // this cannot be done inside of sort because datachanged has already
67910     // fired and will trigger a repaint of the bound view.
67911     doSort: function(sorterFn) {
67912         var me = this;
67913         if (me.remoteSort) {
67914             //the load function will pick up the new sorters and request the sorted data from the proxy
67915             me.load();
67916         } else {
67917             me.data.sortBy(sorterFn);
67918             if (!me.buffered) {
67919                 var range = me.getRange(),
67920                     ln = range.length,
67921                     i  = 0;
67922                 for (; i < ln; i++) {
67923                     range[i].index = i;
67924                 }
67925             }
67926             me.fireEvent('datachanged', me);
67927         }
67928     },
67929
67930     /**
67931      * Finds the index of the first matching Record in this store by a specific field value.
67932      * @param {String} fieldName The name of the Record field to test.
67933      * @param {String/RegExp} value Either a string that the field value
67934      * should begin with, or a RegExp to test against the field.
67935      * @param {Number} startIndex (optional) The index to start searching at
67936      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
67937      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
67938      * @param {Boolean} exactMatch (optional) True to force exact match (^ and $ characters added to the regex). Defaults to false.
67939      * @return {Number} The matched index or -1
67940      */
67941     find: function(property, value, start, anyMatch, caseSensitive, exactMatch) {
67942         var fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
67943         return fn ? this.data.findIndexBy(fn, null, start) : -1;
67944     },
67945
67946     /**
67947      * Finds the first matching Record in this store by a specific field value.
67948      * @param {String} fieldName The name of the Record field to test.
67949      * @param {String/RegExp} value Either a string that the field value
67950      * should begin with, or a RegExp to test against the field.
67951      * @param {Number} startIndex (optional) The index to start searching at
67952      * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
67953      * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
67954      * @param {Boolean} exactMatch (optional) True to force exact match (^ and $ characters added to the regex). Defaults to false.
67955      * @return {Ext.data.Model} The matched record or null
67956      */
67957     findRecord: function() {
67958         var me = this,
67959             index = me.find.apply(me, arguments);
67960         return index !== -1 ? me.getAt(index) : null;
67961     },
67962
67963     /**
67964      * @private
67965      * Returns a filter function used to test a the given property's value. Defers most of the work to
67966      * Ext.util.MixedCollection's createValueMatcher function
67967      * @param {String} property The property to create the filter function for
67968      * @param {String/RegExp} value The string/regex to compare the property value to
67969      * @param {Boolean} [anyMatch=false] True if we don't care if the filter value is not the full value.
67970      * @param {Boolean} [caseSensitive=false] True to create a case-sensitive regex.
67971      * @param {Boolean} [exactMatch=false] True to force exact match (^ and $ characters added to the regex).
67972      * Ignored if anyMatch is true.
67973      */
67974     createFilterFn: function(property, value, anyMatch, caseSensitive, exactMatch) {
67975         if (Ext.isEmpty(value)) {
67976             return false;
67977         }
67978         value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
67979         return function(r) {
67980             return value.test(r.data[property]);
67981         };
67982     },
67983
67984     /**
67985      * Finds the index of the first matching Record in this store by a specific field value.
67986      * @param {String} fieldName The name of the Record field to test.
67987      * @param {Object} value The value to match the field against.
67988      * @param {Number} startIndex (optional) The index to start searching at
67989      * @return {Number} The matched index or -1
67990      */
67991     findExact: function(property, value, start) {
67992         return this.data.findIndexBy(function(rec) {
67993             return rec.get(property) == value;
67994         },
67995         this, start);
67996     },
67997
67998     /**
67999      * Find the index of the first matching Record in this Store by a function.
68000      * If the function returns <tt>true</tt> it is considered a match.
68001      * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
68002      * <li><b>record</b> : Ext.data.Model<p class="sub-desc">The {@link Ext.data.Model record}
68003      * to test for filtering. Access field values using {@link Ext.data.Model#get}.</p></li>
68004      * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
68005      * </ul>
68006      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this Store.
68007      * @param {Number} startIndex (optional) The index to start searching at
68008      * @return {Number} The matched index or -1
68009      */
68010     findBy: function(fn, scope, start) {
68011         return this.data.findIndexBy(fn, scope, start);
68012     },
68013
68014     /**
68015      * Collects unique values for a particular dataIndex from this store.
68016      * @param {String} dataIndex The property to collect
68017      * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
68018      * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
68019      * @return {Object[]} An array of the unique values
68020      **/
68021     collect: function(dataIndex, allowNull, bypassFilter) {
68022         var me = this,
68023             data = (bypassFilter === true && me.snapshot) ? me.snapshot: me.data;
68024
68025         return data.collect(dataIndex, 'data', allowNull);
68026     },
68027
68028     /**
68029      * Gets the number of cached records.
68030      * <p>If using paging, this may not be the total size of the dataset. If the data object
68031      * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
68032      * the dataset size.  <b>Note</b>: see the Important note in {@link #load}.</p>
68033      * @return {Number} The number of Records in the Store's cache.
68034      */
68035     getCount: function() {
68036         return this.data.length || 0;
68037     },
68038
68039     /**
68040      * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}
68041      * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the
68042      * number of records loaded into the Store at the moment, getTotalCount returns the number of records that
68043      * could be loaded into the Store if the Store contained all data
68044      * @return {Number} The total number of Model instances available via the Proxy
68045      */
68046     getTotalCount: function() {
68047         return this.totalCount;
68048     },
68049
68050     /**
68051      * Get the Record at the specified index.
68052      * @param {Number} index The index of the Record to find.
68053      * @return {Ext.data.Model} The Record at the passed index. Returns undefined if not found.
68054      */
68055     getAt: function(index) {
68056         return this.data.getAt(index);
68057     },
68058
68059     /**
68060      * Returns a range of Records between specified indices.
68061      * @param {Number} [startIndex=0] The starting index
68062      * @param {Number} [endIndex] The ending index. Defaults to the last Record in the Store.
68063      * @return {Ext.data.Model[]} An array of Records
68064      */
68065     getRange: function(start, end) {
68066         return this.data.getRange(start, end);
68067     },
68068
68069     /**
68070      * Get the Record with the specified id.
68071      * @param {String} id The id of the Record to find.
68072      * @return {Ext.data.Model} The Record with the passed id. Returns null if not found.
68073      */
68074     getById: function(id) {
68075         return (this.snapshot || this.data).findBy(function(record) {
68076             return record.getId() === id;
68077         });
68078     },
68079
68080     /**
68081      * Get the index within the cache of the passed Record.
68082      * @param {Ext.data.Model} record The Ext.data.Model object to find.
68083      * @return {Number} The index of the passed Record. Returns -1 if not found.
68084      */
68085     indexOf: function(record) {
68086         return this.data.indexOf(record);
68087     },
68088
68089
68090     /**
68091      * Get the index within the entire dataset. From 0 to the totalCount.
68092      * @param {Ext.data.Model} record The Ext.data.Model object to find.
68093      * @return {Number} The index of the passed Record. Returns -1 if not found.
68094      */
68095     indexOfTotal: function(record) {
68096         var index = record.index;
68097         if (index || index === 0) {
68098             return index;
68099         }
68100         return this.indexOf(record);
68101     },
68102
68103     /**
68104      * Get the index within the cache of the Record with the passed id.
68105      * @param {String} id The id of the Record to find.
68106      * @return {Number} The index of the Record. Returns -1 if not found.
68107      */
68108     indexOfId: function(id) {
68109         return this.indexOf(this.getById(id));
68110     },
68111
68112     /**
68113      * Remove all items from the store.
68114      * @param {Boolean} silent Prevent the `clear` event from being fired.
68115      */
68116     removeAll: function(silent) {
68117         var me = this;
68118
68119         me.clearData();
68120         if (me.snapshot) {
68121             me.snapshot.clear();
68122         }
68123         if (silent !== true) {
68124             me.fireEvent('clear', me);
68125         }
68126     },
68127
68128     /*
68129      * Aggregation methods
68130      */
68131
68132     /**
68133      * Convenience function for getting the first model instance in the store
68134      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68135      * in the store. The value returned will be an object literal with the key being the group
68136      * name and the first record being the value. The grouped parameter is only honored if
68137      * the store has a groupField.
68138      * @return {Ext.data.Model/undefined} The first model instance in the store, or undefined
68139      */
68140     first: function(grouped) {
68141         var me = this;
68142
68143         if (grouped && me.isGrouped()) {
68144             return me.aggregate(function(records) {
68145                 return records.length ? records[0] : undefined;
68146             }, me, true);
68147         } else {
68148             return me.data.first();
68149         }
68150     },
68151
68152     /**
68153      * Convenience function for getting the last model instance in the store
68154      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68155      * in the store. The value returned will be an object literal with the key being the group
68156      * name and the last record being the value. The grouped parameter is only honored if
68157      * the store has a groupField.
68158      * @return {Ext.data.Model/undefined} The last model instance in the store, or undefined
68159      */
68160     last: function(grouped) {
68161         var me = this;
68162
68163         if (grouped && me.isGrouped()) {
68164             return me.aggregate(function(records) {
68165                 var len = records.length;
68166                 return len ? records[len - 1] : undefined;
68167             }, me, true);
68168         } else {
68169             return me.data.last();
68170         }
68171     },
68172
68173     /**
68174      * Sums the value of <tt>property</tt> for each {@link Ext.data.Model record} between <tt>start</tt>
68175      * and <tt>end</tt> and returns the result.
68176      * @param {String} field A field in each record
68177      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68178      * in the store. The value returned will be an object literal with the key being the group
68179      * name and the sum for that group being the value. The grouped parameter is only honored if
68180      * the store has a groupField.
68181      * @return {Number} The sum
68182      */
68183     sum: function(field, grouped) {
68184         var me = this;
68185
68186         if (grouped && me.isGrouped()) {
68187             return me.aggregate(me.getSum, me, true, [field]);
68188         } else {
68189             return me.getSum(me.data.items, field);
68190         }
68191     },
68192
68193     // @private, see sum
68194     getSum: function(records, field) {
68195         var total = 0,
68196             i = 0,
68197             len = records.length;
68198
68199         for (; i < len; ++i) {
68200             total += records[i].get(field);
68201         }
68202
68203         return total;
68204     },
68205
68206     /**
68207      * Gets the count of items in the store.
68208      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68209      * in the store. The value returned will be an object literal with the key being the group
68210      * name and the count for each group being the value. The grouped parameter is only honored if
68211      * the store has a groupField.
68212      * @return {Number} the count
68213      */
68214     count: function(grouped) {
68215         var me = this;
68216
68217         if (grouped && me.isGrouped()) {
68218             return me.aggregate(function(records) {
68219                 return records.length;
68220             }, me, true);
68221         } else {
68222             return me.getCount();
68223         }
68224     },
68225
68226     /**
68227      * Gets the minimum value in the store.
68228      * @param {String} field The field in each record
68229      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68230      * in the store. The value returned will be an object literal with the key being the group
68231      * name and the minimum in the group being the value. The grouped parameter is only honored if
68232      * the store has a groupField.
68233      * @return {Object} The minimum value, if no items exist, undefined.
68234      */
68235     min: function(field, grouped) {
68236         var me = this;
68237
68238         if (grouped && me.isGrouped()) {
68239             return me.aggregate(me.getMin, me, true, [field]);
68240         } else {
68241             return me.getMin(me.data.items, field);
68242         }
68243     },
68244
68245     // @private, see min
68246     getMin: function(records, field){
68247         var i = 1,
68248             len = records.length,
68249             value, min;
68250
68251         if (len > 0) {
68252             min = records[0].get(field);
68253         }
68254
68255         for (; i < len; ++i) {
68256             value = records[i].get(field);
68257             if (value < min) {
68258                 min = value;
68259             }
68260         }
68261         return min;
68262     },
68263
68264     /**
68265      * Gets the maximum value in the store.
68266      * @param {String} field The field in each record
68267      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68268      * in the store. The value returned will be an object literal with the key being the group
68269      * name and the maximum in the group being the value. The grouped parameter is only honored if
68270      * the store has a groupField.
68271      * @return {Object} The maximum value, if no items exist, undefined.
68272      */
68273     max: function(field, grouped) {
68274         var me = this;
68275
68276         if (grouped && me.isGrouped()) {
68277             return me.aggregate(me.getMax, me, true, [field]);
68278         } else {
68279             return me.getMax(me.data.items, field);
68280         }
68281     },
68282
68283     // @private, see max
68284     getMax: function(records, field) {
68285         var i = 1,
68286             len = records.length,
68287             value,
68288             max;
68289
68290         if (len > 0) {
68291             max = records[0].get(field);
68292         }
68293
68294         for (; i < len; ++i) {
68295             value = records[i].get(field);
68296             if (value > max) {
68297                 max = value;
68298             }
68299         }
68300         return max;
68301     },
68302
68303     /**
68304      * Gets the average value in the store.
68305      * @param {String} field The field in each record
68306      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68307      * in the store. The value returned will be an object literal with the key being the group
68308      * name and the group average being the value. The grouped parameter is only honored if
68309      * the store has a groupField.
68310      * @return {Object} The average value, if no items exist, 0.
68311      */
68312     average: function(field, grouped) {
68313         var me = this;
68314         if (grouped && me.isGrouped()) {
68315             return me.aggregate(me.getAverage, me, true, [field]);
68316         } else {
68317             return me.getAverage(me.data.items, field);
68318         }
68319     },
68320
68321     // @private, see average
68322     getAverage: function(records, field) {
68323         var i = 0,
68324             len = records.length,
68325             sum = 0;
68326
68327         if (records.length > 0) {
68328             for (; i < len; ++i) {
68329                 sum += records[i].get(field);
68330             }
68331             return sum / len;
68332         }
68333         return 0;
68334     },
68335
68336     /**
68337      * Runs the aggregate function for all the records in the store.
68338      * @param {Function} fn The function to execute. The function is called with a single parameter,
68339      * an array of records for that group.
68340      * @param {Object} scope (optional) The scope to execute the function in. Defaults to the store.
68341      * @param {Boolean} grouped (Optional) True to perform the operation for each group
68342      * in the store. The value returned will be an object literal with the key being the group
68343      * name and the group average being the value. The grouped parameter is only honored if
68344      * the store has a groupField.
68345      * @param {Array} args (optional) Any arguments to append to the function call
68346      * @return {Object} An object literal with the group names and their appropriate values.
68347      */
68348     aggregate: function(fn, scope, grouped, args) {
68349         args = args || [];
68350         if (grouped && this.isGrouped()) {
68351             var groups = this.getGroups(),
68352                 i = 0,
68353                 len = groups.length,
68354                 out = {},
68355                 group;
68356
68357             for (; i < len; ++i) {
68358                 group = groups[i];
68359                 out[group.name] = fn.apply(scope || this, [group.children].concat(args));
68360             }
68361             return out;
68362         } else {
68363             return fn.apply(scope || this, [this.data.items].concat(args));
68364         }
68365     }
68366 }, function() {
68367     // A dummy empty store with a fieldless Model defined in it.
68368     // Just for binding to Views which are instantiated with no Store defined.
68369     // They will be able to run and render fine, and be bound to a generated Store later.
68370     Ext.regStore('ext-empty-store', {fields: [], proxy: 'proxy'});
68371 });
68372
68373 /**
68374  * @author Ed Spencer
68375  * @class Ext.data.JsonStore
68376  * @extends Ext.data.Store
68377  * @ignore
68378  *
68379  * <p>Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
68380  * A JsonStore will be automatically configured with a {@link Ext.data.reader.Json}.</p>
68381  *
68382  * <p>A store configuration would be something like:</p>
68383  *
68384 <pre><code>
68385 var store = new Ext.data.JsonStore({
68386     // store configs
68387     autoDestroy: true,
68388     storeId: 'myStore',
68389
68390     proxy: {
68391         type: 'ajax',
68392         url: 'get-images.php',
68393         reader: {
68394             type: 'json',
68395             root: 'images',
68396             idProperty: 'name'
68397         }
68398     },
68399
68400     //alternatively, a {@link Ext.data.Model} name can be given (see {@link Ext.data.Store} for an example)
68401     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
68402 });
68403 </code></pre>
68404  *
68405  * <p>This store is configured to consume a returned object of the form:<pre><code>
68406 {
68407     images: [
68408         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
68409         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
68410     ]
68411 }
68412 </code></pre>
68413  *
68414  * <p>An object literal of this form could also be used as the {@link #data} config option.</p>
68415  *
68416  * @xtype jsonstore
68417  */
68418 Ext.define('Ext.data.JsonStore',  {
68419     extend: 'Ext.data.Store',
68420     alias: 'store.json',
68421
68422     /**
68423      * @cfg {Ext.data.DataReader} reader @hide
68424      */
68425     constructor: function(config) {
68426         config = config || {};
68427
68428         Ext.applyIf(config, {
68429             proxy: {
68430                 type  : 'ajax',
68431                 reader: 'json',
68432                 writer: 'json'
68433             }
68434         });
68435
68436         this.callParent([config]);
68437     }
68438 });
68439
68440 /**
68441  * @class Ext.chart.axis.Time
68442  * @extends Ext.chart.axis.Numeric
68443  *
68444  * A type of axis whose units are measured in time values. Use this axis
68445  * for listing dates that you will want to group or dynamically change.
68446  * If you just want to display dates as categories then use the
68447  * Category class for axis instead.
68448  *
68449  * For example:
68450  *
68451  *     axes: [{
68452  *         type: 'Time',
68453  *         position: 'bottom',
68454  *         fields: 'date',
68455  *         title: 'Day',
68456  *         dateFormat: 'M d',
68457  *
68458  *         constrain: true,
68459  *         fromDate: new Date('1/1/11'),
68460  *         toDate: new Date('1/7/11')
68461  *     }]
68462  *
68463  * In this example we're creating a time axis that has as title *Day*.
68464  * The field the axis is bound to is `date`.
68465  * The date format to use to display the text for the axis labels is `M d`
68466  * which is a three letter month abbreviation followed by the day number.
68467  * The time axis will show values for dates between `fromDate` and `toDate`.
68468  * Since `constrain` is set to true all other values for other dates not between
68469  * the fromDate and toDate will not be displayed.
68470  *
68471  */
68472 Ext.define('Ext.chart.axis.Time', {
68473
68474     /* Begin Definitions */
68475
68476     extend: 'Ext.chart.axis.Numeric',
68477
68478     alternateClassName: 'Ext.chart.TimeAxis',
68479
68480     alias: 'axis.time',
68481
68482     requires: ['Ext.data.Store', 'Ext.data.JsonStore'],
68483
68484     /* End Definitions */
68485
68486     /**
68487      * @cfg {String/Boolean} dateFormat
68488      * Indicates the format the date will be rendered on.
68489      * For example: 'M d' will render the dates as 'Jan 30', etc.
68490      * For a list of possible format strings see {@link Ext.Date Date}
68491      */
68492     dateFormat: false,
68493
68494     /**
68495      * @cfg {Date} fromDate The starting date for the time axis.
68496      */
68497     fromDate: false,
68498
68499     /**
68500      * @cfg {Date} toDate The ending date for the time axis.
68501      */
68502     toDate: false,
68503
68504     /**
68505      * @cfg {Array/Boolean} step
68506      * An array with two components: The first is the unit of the step (day, month, year, etc).
68507      * The second one is the number of units for the step (1, 2, etc.).
68508      * Defaults to `[Ext.Date.DAY, 1]`.
68509      */
68510     step: [Ext.Date.DAY, 1],
68511     
68512     /**
68513      * @cfg {Boolean} constrain
68514      * If true, the values of the chart will be rendered only if they belong between the fromDate and toDate.
68515      * If false, the time axis will adapt to the new values by adding/removing steps.
68516      */
68517     constrain: false,
68518
68519     // Avoid roundtoDecimal call in Numeric Axis's constructor
68520     roundToDecimal: false,
68521     
68522     constructor: function (config) {
68523         var me = this, label, f, df;
68524         me.callParent([config]);
68525         label = me.label || {};
68526         df = this.dateFormat;
68527         if (df) {
68528             if (label.renderer) {
68529                 f = label.renderer;
68530                 label.renderer = function(v) {
68531                     v = f(v);
68532                     return Ext.Date.format(new Date(f(v)), df);
68533                 };
68534             } else {
68535                 label.renderer = function(v) {
68536                     return Ext.Date.format(new Date(v >> 0), df);
68537                 };
68538             }
68539         }
68540     },
68541
68542     doConstrain: function () {
68543         var me = this,
68544             store = me.chart.store,
68545             data = [],
68546             series = me.chart.series.items,
68547             math = Math,
68548             mmax = math.max,
68549             mmin = math.min,
68550             fields = me.fields,
68551             ln = fields.length,
68552             range = me.getRange(),
68553             min = range.min, max = range.max, i, l, excludes = [],
68554             value, values, rec, data = [];
68555         for (i = 0, l = series.length; i < l; i++) {
68556             excludes[i] = series[i].__excludes;
68557         }
68558         store.each(function(record) {
68559             for (i = 0; i < ln; i++) {
68560                 if (excludes[i]) {
68561                     continue;
68562                 }
68563                 value = record.get(fields[i]);
68564                 if (+value < +min) return;
68565                 if (+value > +max) return;
68566             }
68567             data.push(record);
68568         })
68569         me.chart.substore = Ext.create('Ext.data.JsonStore', { model: store.model, data: data });
68570     },
68571
68572     // Before rendering, set current default step count to be number of records.
68573     processView: function () {
68574         var me = this;
68575         if (me.fromDate) {
68576             me.minimum = +me.fromDate;
68577         }
68578         if (me.toDate) {
68579             me.maximum = +me.toDate;
68580         }
68581         if (me.constrain) {
68582             me.doConstrain();
68583         }
68584      },
68585
68586     // @private modifies the store and creates the labels for the axes.
68587     calcEnds: function() {
68588         var me = this, range, step = me.step;
68589         if (step) {
68590             range = me.getRange();
68591             range = Ext.draw.Draw.snapEndsByDateAndStep(new Date(range.min), new Date(range.max), Ext.isNumber(step) ? [Date.MILLI, step]: step);
68592             if (me.minimum) {
68593                 range.from = me.minimum;
68594             }
68595             if (me.maximum) {
68596                 range.to = me.maximum;
68597             }
68598             range.step = (range.to - range.from) / range.steps;
68599             return range;
68600         } else {
68601             return me.callParent(arguments);
68602         }
68603     }
68604  });
68605
68606
68607 /**
68608  * @class Ext.chart.series.Series
68609  *
68610  * Series is the abstract class containing the common logic to all chart series. Series includes
68611  * methods from Labels, Highlights, Tips and Callouts mixins. This class implements the logic of handling
68612  * mouse events, animating, hiding, showing all elements and returning the color of the series to be used as a legend item.
68613  *
68614  * ## Listeners
68615  *
68616  * The series class supports listeners via the Observable syntax. Some of these listeners are:
68617  *
68618  *  - `itemmouseup` When the user interacts with a marker.
68619  *  - `itemmousedown` When the user interacts with a marker.
68620  *  - `itemmousemove` When the user iteracts with a marker.
68621  *  - `afterrender` Will be triggered when the animation ends or when the series has been rendered completely.
68622  *
68623  * For example:
68624  *
68625  *     series: [{
68626  *             type: 'column',
68627  *             axis: 'left',
68628  *             listeners: {
68629  *                     'afterrender': function() {
68630  *                             console('afterrender');
68631  *                     }
68632  *             },
68633  *             xField: 'category',
68634  *             yField: 'data1'
68635  *     }]
68636  */
68637 Ext.define('Ext.chart.series.Series', {
68638
68639     /* Begin Definitions */
68640
68641     mixins: {
68642         observable: 'Ext.util.Observable',
68643         labels: 'Ext.chart.Label',
68644         highlights: 'Ext.chart.Highlight',
68645         tips: 'Ext.chart.Tip',
68646         callouts: 'Ext.chart.Callout'
68647     },
68648
68649     /* End Definitions */
68650
68651     /**
68652      * @cfg {Boolean/Object} highlight
68653      * If set to `true` it will highlight the markers or the series when hovering
68654      * with the mouse. This parameter can also be an object with the same style
68655      * properties you would apply to a {@link Ext.draw.Sprite} to apply custom
68656      * styles to markers and series.
68657      */
68658
68659     /**
68660      * @cfg {Object} tips
68661      * Add tooltips to the visualization's markers. The options for the tips are the
68662      * same configuration used with {@link Ext.tip.ToolTip}. For example:
68663      *
68664      *     tips: {
68665      *       trackMouse: true,
68666      *       width: 140,
68667      *       height: 28,
68668      *       renderer: function(storeItem, item) {
68669      *         this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
68670      *       }
68671      *     },
68672      */
68673
68674     /**
68675      * @cfg {String} type
68676      * The type of series. Set in subclasses.
68677      */
68678     type: null,
68679
68680     /**
68681      * @cfg {String} title
68682      * The human-readable name of the series.
68683      */
68684     title: null,
68685
68686     /**
68687      * @cfg {Boolean} showInLegend
68688      * Whether to show this series in the legend.
68689      */
68690     showInLegend: true,
68691
68692     /**
68693      * @cfg {Function} renderer
68694      * A function that can be overridden to set custom styling properties to each rendered element.
68695      * Passes in (sprite, record, attributes, index, store) to the function.
68696      */
68697     renderer: function(sprite, record, attributes, index, store) {
68698         return attributes;
68699     },
68700
68701     /**
68702      * @cfg {Array} shadowAttributes
68703      * An array with shadow attributes
68704      */
68705     shadowAttributes: null,
68706
68707     //@private triggerdrawlistener flag
68708     triggerAfterDraw: false,
68709
68710     /**
68711      * @cfg {Object} listeners
68712      * An (optional) object with event callbacks. All event callbacks get the target *item* as first parameter. The callback functions are:
68713      *
68714      *  - itemmouseover
68715      *  - itemmouseout
68716      *  - itemmousedown
68717      *  - itemmouseup
68718      */
68719
68720     constructor: function(config) {
68721         var me = this;
68722         if (config) {
68723             Ext.apply(me, config);
68724         }
68725
68726         me.shadowGroups = [];
68727
68728         me.mixins.labels.constructor.call(me, config);
68729         me.mixins.highlights.constructor.call(me, config);
68730         me.mixins.tips.constructor.call(me, config);
68731         me.mixins.callouts.constructor.call(me, config);
68732
68733         me.addEvents({
68734             scope: me,
68735             itemmouseover: true,
68736             itemmouseout: true,
68737             itemmousedown: true,
68738             itemmouseup: true,
68739             mouseleave: true,
68740             afterdraw: true,
68741
68742             /**
68743              * @event titlechange
68744              * Fires when the series title is changed via {@link #setTitle}.
68745              * @param {String} title The new title value
68746              * @param {Number} index The index in the collection of titles
68747              */
68748             titlechange: true
68749         });
68750
68751         me.mixins.observable.constructor.call(me, config);
68752
68753         me.on({
68754             scope: me,
68755             itemmouseover: me.onItemMouseOver,
68756             itemmouseout: me.onItemMouseOut,
68757             mouseleave: me.onMouseLeave
68758         });
68759     },
68760     
68761     /**
68762      * Iterate over each of the records for this series. The default implementation simply iterates
68763      * through the entire data store, but individual series implementations can override this to
68764      * provide custom handling, e.g. adding/removing records.
68765      * @param {Function} fn The function to execute for each record.
68766      * @param {Object} scope Scope for the fn.
68767      */
68768     eachRecord: function(fn, scope) {
68769         var chart = this.chart;
68770         (chart.substore || chart.store).each(fn, scope);
68771     },
68772
68773     /**
68774      * Return the number of records being displayed in this series. Defaults to the number of
68775      * records in the store; individual series implementations can override to provide custom handling.
68776      */
68777     getRecordCount: function() {
68778         var chart = this.chart,
68779             store = chart.substore || chart.store;
68780         return store ? store.getCount() : 0;
68781     },
68782
68783     /**
68784      * Determines whether the series item at the given index has been excluded, i.e. toggled off in the legend.
68785      * @param index
68786      */
68787     isExcluded: function(index) {
68788         var excludes = this.__excludes;
68789         return !!(excludes && excludes[index]);
68790     },
68791
68792     // @private set the bbox and clipBox for the series
68793     setBBox: function(noGutter) {
68794         var me = this,
68795             chart = me.chart,
68796             chartBBox = chart.chartBBox,
68797             gutterX = noGutter ? 0 : chart.maxGutter[0],
68798             gutterY = noGutter ? 0 : chart.maxGutter[1],
68799             clipBox, bbox;
68800
68801         clipBox = {
68802             x: chartBBox.x,
68803             y: chartBBox.y,
68804             width: chartBBox.width,
68805             height: chartBBox.height
68806         };
68807         me.clipBox = clipBox;
68808
68809         bbox = {
68810             x: (clipBox.x + gutterX) - (chart.zoom.x * chart.zoom.width),
68811             y: (clipBox.y + gutterY) - (chart.zoom.y * chart.zoom.height),
68812             width: (clipBox.width - (gutterX * 2)) * chart.zoom.width,
68813             height: (clipBox.height - (gutterY * 2)) * chart.zoom.height
68814         };
68815         me.bbox = bbox;
68816     },
68817
68818     // @private set the animation for the sprite
68819     onAnimate: function(sprite, attr) {
68820         var me = this;
68821         sprite.stopAnimation();
68822         if (me.triggerAfterDraw) {
68823             return sprite.animate(Ext.applyIf(attr, me.chart.animate));
68824         } else {
68825             me.triggerAfterDraw = true;
68826             return sprite.animate(Ext.apply(Ext.applyIf(attr, me.chart.animate), {
68827                 listeners: {
68828                     'afteranimate': function() {
68829                         me.triggerAfterDraw = false;
68830                         me.fireEvent('afterrender');
68831                     }
68832                 }
68833             }));
68834         }
68835     },
68836
68837     // @private return the gutter.
68838     getGutters: function() {
68839         return [0, 0];
68840     },
68841
68842     // @private wrapper for the itemmouseover event.
68843     onItemMouseOver: function(item) {
68844         var me = this;
68845         if (item.series === me) {
68846             if (me.highlight) {
68847                 me.highlightItem(item);
68848             }
68849             if (me.tooltip) {
68850                 me.showTip(item);
68851             }
68852         }
68853     },
68854
68855     // @private wrapper for the itemmouseout event.
68856     onItemMouseOut: function(item) {
68857         var me = this;
68858         if (item.series === me) {
68859             me.unHighlightItem();
68860             if (me.tooltip) {
68861                 me.hideTip(item);
68862             }
68863         }
68864     },
68865
68866     // @private wrapper for the mouseleave event.
68867     onMouseLeave: function() {
68868         var me = this;
68869         me.unHighlightItem();
68870         if (me.tooltip) {
68871             me.hideTip();
68872         }
68873     },
68874
68875     /**
68876      * For a given x/y point relative to the Surface, find a corresponding item from this
68877      * series, if any.
68878      * @param {Number} x
68879      * @param {Number} y
68880      * @return {Object} An object describing the item, or null if there is no matching item.
68881      * The exact contents of this object will vary by series type, but should always contain the following:
68882      * @return {Ext.chart.series.Series} return.series the Series object to which the item belongs
68883      * @return {Object} return.value the value(s) of the item's data point
68884      * @return {Array} return.point the x/y coordinates relative to the chart box of a single point
68885      * for this data item, which can be used as e.g. a tooltip anchor point.
68886      * @return {Ext.draw.Sprite} return.sprite the item's rendering Sprite.
68887      */
68888     getItemForPoint: function(x, y) {
68889         //if there are no items to query just return null.
68890         if (!this.items || !this.items.length || this.seriesIsHidden) {
68891             return null;
68892         }
68893         var me = this,
68894             items = me.items,
68895             bbox = me.bbox,
68896             item, i, ln;
68897         // Check bounds
68898         if (!Ext.draw.Draw.withinBox(x, y, bbox)) {
68899             return null;
68900         }
68901         for (i = 0, ln = items.length; i < ln; i++) {
68902             if (items[i] && this.isItemInPoint(x, y, items[i], i)) {
68903                 return items[i];
68904             }
68905         }
68906
68907         return null;
68908     },
68909
68910     isItemInPoint: function(x, y, item, i) {
68911         return false;
68912     },
68913
68914     /**
68915      * Hides all the elements in the series.
68916      */
68917     hideAll: function() {
68918         var me = this,
68919             items = me.items,
68920             item, len, i, j, l, sprite, shadows;
68921
68922         me.seriesIsHidden = true;
68923         me._prevShowMarkers = me.showMarkers;
68924
68925         me.showMarkers = false;
68926         //hide all labels
68927         me.hideLabels(0);
68928         //hide all sprites
68929         for (i = 0, len = items.length; i < len; i++) {
68930             item = items[i];
68931             sprite = item.sprite;
68932             if (sprite) {
68933                 sprite.setAttributes({
68934                     hidden: true
68935                 }, true);
68936             }
68937
68938             if (sprite && sprite.shadows) {
68939                 shadows = sprite.shadows;
68940                 for (j = 0, l = shadows.length; j < l; ++j) {
68941                     shadows[j].setAttributes({
68942                         hidden: true
68943                     }, true);
68944                 }
68945             }
68946         }
68947     },
68948
68949     /**
68950      * Shows all the elements in the series.
68951      */
68952     showAll: function() {
68953         var me = this,
68954             prevAnimate = me.chart.animate;
68955         me.chart.animate = false;
68956         me.seriesIsHidden = false;
68957         me.showMarkers = me._prevShowMarkers;
68958         me.drawSeries();
68959         me.chart.animate = prevAnimate;
68960     },
68961
68962     /**
68963      * Returns a string with the color to be used for the series legend item.
68964      */
68965     getLegendColor: function(index) {
68966         var me = this, fill, stroke;
68967         if (me.seriesStyle) {
68968             fill = me.seriesStyle.fill;
68969             stroke = me.seriesStyle.stroke;
68970             if (fill && fill != 'none') {
68971                 return fill;
68972             }
68973             return stroke;
68974         }
68975         return '#000';
68976     },
68977
68978     /**
68979      * Checks whether the data field should be visible in the legend
68980      * @private
68981      * @param {Number} index The index of the current item
68982      */
68983     visibleInLegend: function(index){
68984         var excludes = this.__excludes;
68985         if (excludes) {
68986             return !excludes[index];
68987         }
68988         return !this.seriesIsHidden;
68989     },
68990
68991     /**
68992      * Changes the value of the {@link #title} for the series.
68993      * Arguments can take two forms:
68994      * <ul>
68995      * <li>A single String value: this will be used as the new single title for the series (applies
68996      * to series with only one yField)</li>
68997      * <li>A numeric index and a String value: this will set the title for a single indexed yField.</li>
68998      * </ul>
68999      * @param {Number} index
69000      * @param {String} title
69001      */
69002     setTitle: function(index, title) {
69003         var me = this,
69004             oldTitle = me.title;
69005
69006         if (Ext.isString(index)) {
69007             title = index;
69008             index = 0;
69009         }
69010
69011         if (Ext.isArray(oldTitle)) {
69012             oldTitle[index] = title;
69013         } else {
69014             me.title = title;
69015         }
69016
69017         me.fireEvent('titlechange', title, index);
69018     }
69019 });
69020
69021 /**
69022  * @class Ext.chart.series.Cartesian
69023  * @extends Ext.chart.series.Series
69024  *
69025  * Common base class for series implementations which plot values using x/y coordinates.
69026  */
69027 Ext.define('Ext.chart.series.Cartesian', {
69028
69029     /* Begin Definitions */
69030
69031     extend: 'Ext.chart.series.Series',
69032
69033     alternateClassName: ['Ext.chart.CartesianSeries', 'Ext.chart.CartesianChart'],
69034
69035     /* End Definitions */
69036
69037     /**
69038      * The field used to access the x axis value from the items from the data
69039      * source.
69040      *
69041      * @cfg xField
69042      * @type String
69043      */
69044     xField: null,
69045
69046     /**
69047      * The field used to access the y-axis value from the items from the data
69048      * source.
69049      *
69050      * @cfg yField
69051      * @type String
69052      */
69053     yField: null,
69054
69055     /**
69056      * @cfg {String} axis
69057      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
69058      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
69059      * relative scale will be used.
69060      */
69061     axis: 'left',
69062
69063     getLegendLabels: function() {
69064         var me = this,
69065             labels = [],
69066             combinations = me.combinations;
69067
69068         Ext.each([].concat(me.yField), function(yField, i) {
69069             var title = me.title;
69070             // Use the 'title' config if present, otherwise use the raw yField name
69071             labels.push((Ext.isArray(title) ? title[i] : title) || yField);
69072         });
69073
69074         // Handle yFields combined via legend drag-drop
69075         if (combinations) {
69076             Ext.each(combinations, function(combo) {
69077                 var label0 = labels[combo[0]],
69078                     label1 = labels[combo[1]];
69079                 labels[combo[1]] = label0 + ' & ' + label1;
69080                 labels.splice(combo[0], 1);
69081             });
69082         }
69083
69084         return labels;
69085     },
69086
69087     /**
69088      * @protected Iterates over a given record's values for each of this series's yFields,
69089      * executing a given function for each value. Any yFields that have been combined
69090      * via legend drag-drop will be treated as a single value.
69091      * @param {Ext.data.Model} record
69092      * @param {Function} fn
69093      * @param {Object} scope
69094      */
69095     eachYValue: function(record, fn, scope) {
69096         Ext.each(this.getYValueAccessors(), function(accessor, i) {
69097             fn.call(scope, accessor(record), i);
69098         });
69099     },
69100
69101     /**
69102      * @protected Returns the number of yField values, taking into account fields combined
69103      * via legend drag-drop.
69104      * @return {Number}
69105      */
69106     getYValueCount: function() {
69107         return this.getYValueAccessors().length;
69108     },
69109
69110     combine: function(index1, index2) {
69111         var me = this,
69112             accessors = me.getYValueAccessors(),
69113             accessor1 = accessors[index1],
69114             accessor2 = accessors[index2];
69115
69116         // Combine the yValue accessors for the two indexes into a single accessor that returns their sum
69117         accessors[index2] = function(record) {
69118             return accessor1(record) + accessor2(record);
69119         };
69120         accessors.splice(index1, 1);
69121
69122         me.callParent([index1, index2]);
69123     },
69124
69125     clearCombinations: function() {
69126         // Clear combined accessors, they'll get regenerated on next call to getYValueAccessors
69127         delete this.yValueAccessors;
69128         this.callParent();
69129     },
69130
69131     /**
69132      * @protected Returns an array of functions, each of which returns the value of the yField
69133      * corresponding to function's index in the array, for a given record (each function takes the
69134      * record as its only argument.) If yFields have been combined by the user via legend drag-drop,
69135      * this list of accessors will be kept in sync with those combinations.
69136      * @return {Array} array of accessor functions
69137      */
69138     getYValueAccessors: function() {
69139         var me = this,
69140             accessors = me.yValueAccessors;
69141         if (!accessors) {
69142             accessors = me.yValueAccessors = [];
69143             Ext.each([].concat(me.yField), function(yField) {
69144                 accessors.push(function(record) {
69145                     return record.get(yField);
69146                 });
69147             });
69148         }
69149         return accessors;
69150     },
69151
69152     /**
69153      * Calculate the min and max values for this series's xField.
69154      * @return {Array} [min, max]
69155      */
69156     getMinMaxXValues: function() {
69157         var me = this,
69158             min, max,
69159             xField = me.xField;
69160
69161         if (me.getRecordCount() > 0) {
69162             min = Infinity;
69163             max = -min;
69164             me.eachRecord(function(record) {
69165                 var xValue = record.get(xField);
69166                 if (xValue > max) {
69167                     max = xValue;
69168                 }
69169                 if (xValue < min) {
69170                     min = xValue;
69171                 }
69172             });
69173         } else {
69174             min = max = 0;
69175         }
69176         return [min, max];
69177     },
69178
69179     /**
69180      * Calculate the min and max values for this series's yField(s). Takes into account yField
69181      * combinations, exclusions, and stacking.
69182      * @return {Array} [min, max]
69183      */
69184     getMinMaxYValues: function() {
69185         var me = this,
69186             stacked = me.stacked,
69187             min, max,
69188             positiveTotal, negativeTotal;
69189
69190         function eachYValueStacked(yValue, i) {
69191             if (!me.isExcluded(i)) {
69192                 if (yValue < 0) {
69193                     negativeTotal += yValue;
69194                 } else {
69195                     positiveTotal += yValue;
69196                 }
69197             }
69198         }
69199
69200         function eachYValue(yValue, i) {
69201             if (!me.isExcluded(i)) {
69202                 if (yValue > max) {
69203                     max = yValue;
69204                 }
69205                 if (yValue < min) {
69206                     min = yValue;
69207                 }
69208             }
69209         }
69210
69211         if (me.getRecordCount() > 0) {
69212             min = Infinity;
69213             max = -min;
69214             me.eachRecord(function(record) {
69215                 if (stacked) {
69216                     positiveTotal = 0;
69217                     negativeTotal = 0;
69218                     me.eachYValue(record, eachYValueStacked);
69219                     if (positiveTotal > max) {
69220                         max = positiveTotal;
69221                     }
69222                     if (negativeTotal < min) {
69223                         min = negativeTotal;
69224                     }
69225                 } else {
69226                     me.eachYValue(record, eachYValue);
69227                 }
69228             });
69229         } else {
69230             min = max = 0;
69231         }
69232         return [min, max];
69233     },
69234
69235     getAxesForXAndYFields: function() {
69236         var me = this,
69237             axes = me.chart.axes,
69238             axis = [].concat(me.axis),
69239             xAxis, yAxis;
69240
69241         if (Ext.Array.indexOf(axis, 'top') > -1) {
69242             xAxis = 'top';
69243         } else if (Ext.Array.indexOf(axis, 'bottom') > -1) {
69244             xAxis = 'bottom';
69245         } else {
69246             if (axes.get('top')) {
69247                 xAxis = 'top';
69248             } else if (axes.get('bottom')) {
69249                 xAxis = 'bottom';
69250             }
69251         }
69252
69253         if (Ext.Array.indexOf(axis, 'left') > -1) {
69254             yAxis = 'left';
69255         } else if (Ext.Array.indexOf(axis, 'right') > -1) {
69256             yAxis = 'right';
69257         } else {
69258             if (axes.get('left')) {
69259                 yAxis = 'left';
69260             } else if (axes.get('right')) {
69261                 yAxis = 'right';
69262             }
69263         }
69264
69265         return {
69266             xAxis: xAxis,
69267             yAxis: yAxis
69268         };
69269     }
69270
69271
69272 });
69273
69274 /**
69275  * @class Ext.chart.series.Area
69276  * @extends Ext.chart.series.Cartesian
69277  *
69278  * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
69279  * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
69280  * documentation for more information. A typical configuration object for the area series could be:
69281  *
69282  *     @example
69283  *     var store = Ext.create('Ext.data.JsonStore', {
69284  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
69285  *         data: [
69286  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
69287  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
69288  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
69289  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
69290  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
69291  *         ]
69292  *     });
69293  *
69294  *     Ext.create('Ext.chart.Chart', {
69295  *         renderTo: Ext.getBody(),
69296  *         width: 500,
69297  *         height: 300,
69298  *         store: store,
69299  *         axes: [
69300  *             {
69301  *                 type: 'Numeric',
69302  *                 grid: true,
69303  *                 position: 'left',
69304  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
69305  *                 title: 'Sample Values',
69306  *                 grid: {
69307  *                     odd: {
69308  *                         opacity: 1,
69309  *                         fill: '#ddd',
69310  *                         stroke: '#bbb',
69311  *                         'stroke-width': 1
69312  *                     }
69313  *                 },
69314  *                 minimum: 0,
69315  *                 adjustMinimumByMajorUnit: 0
69316  *             },
69317  *             {
69318  *                 type: 'Category',
69319  *                 position: 'bottom',
69320  *                 fields: ['name'],
69321  *                 title: 'Sample Metrics',
69322  *                 grid: true,
69323  *                 label: {
69324  *                     rotate: {
69325  *                         degrees: 315
69326  *                     }
69327  *                 }
69328  *             }
69329  *         ],
69330  *         series: [{
69331  *             type: 'area',
69332  *             highlight: false,
69333  *             axis: 'left',
69334  *             xField: 'name',
69335  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
69336  *             style: {
69337  *                 opacity: 0.93
69338  *             }
69339  *         }]
69340  *     });
69341  *
69342  * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
69343  * 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,
69344  * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
69345  * to the style object.
69346  *
69347  * @xtype area
69348  */
69349 Ext.define('Ext.chart.series.Area', {
69350
69351     /* Begin Definitions */
69352
69353     extend: 'Ext.chart.series.Cartesian',
69354
69355     alias: 'series.area',
69356
69357     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
69358
69359     /* End Definitions */
69360
69361     type: 'area',
69362
69363     // @private Area charts are alyways stacked
69364     stacked: true,
69365
69366     /**
69367      * @cfg {Object} style
69368      * Append styling properties to this object for it to override theme properties.
69369      */
69370     style: {},
69371
69372     constructor: function(config) {
69373         this.callParent(arguments);
69374         var me = this,
69375             surface = me.chart.surface,
69376             i, l;
69377         Ext.apply(me, config, {
69378             __excludes: [],
69379             highlightCfg: {
69380                 lineWidth: 3,
69381                 stroke: '#55c',
69382                 opacity: 0.8,
69383                 color: '#f00'
69384             }
69385         });
69386         if (me.highlight) {
69387             me.highlightSprite = surface.add({
69388                 type: 'path',
69389                 path: ['M', 0, 0],
69390                 zIndex: 1000,
69391                 opacity: 0.3,
69392                 lineWidth: 5,
69393                 hidden: true,
69394                 stroke: '#444'
69395             });
69396         }
69397         me.group = surface.getGroup(me.seriesId);
69398     },
69399
69400     // @private Shrinks dataSets down to a smaller size
69401     shrink: function(xValues, yValues, size) {
69402         var len = xValues.length,
69403             ratio = Math.floor(len / size),
69404             i, j,
69405             xSum = 0,
69406             yCompLen = this.areas.length,
69407             ySum = [],
69408             xRes = [],
69409             yRes = [];
69410         //initialize array
69411         for (j = 0; j < yCompLen; ++j) {
69412             ySum[j] = 0;
69413         }
69414         for (i = 0; i < len; ++i) {
69415             xSum += xValues[i];
69416             for (j = 0; j < yCompLen; ++j) {
69417                 ySum[j] += yValues[i][j];
69418             }
69419             if (i % ratio == 0) {
69420                 //push averages
69421                 xRes.push(xSum/ratio);
69422                 for (j = 0; j < yCompLen; ++j) {
69423                     ySum[j] /= ratio;
69424                 }
69425                 yRes.push(ySum);
69426                 //reset sum accumulators
69427                 xSum = 0;
69428                 for (j = 0, ySum = []; j < yCompLen; ++j) {
69429                     ySum[j] = 0;
69430                 }
69431             }
69432         }
69433         return {
69434             x: xRes,
69435             y: yRes
69436         };
69437     },
69438
69439     // @private Get chart and data boundaries
69440     getBounds: function() {
69441         var me = this,
69442             chart = me.chart,
69443             store = chart.getChartStore(),
69444             areas = [].concat(me.yField),
69445             areasLen = areas.length,
69446             xValues = [],
69447             yValues = [],
69448             infinity = Infinity,
69449             minX = infinity,
69450             minY = infinity,
69451             maxX = -infinity,
69452             maxY = -infinity,
69453             math = Math,
69454             mmin = math.min,
69455             mmax = math.max,
69456             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
69457
69458         me.setBBox();
69459         bbox = me.bbox;
69460
69461         // Run through the axis
69462         if (me.axis) {
69463             axis = chart.axes.get(me.axis);
69464             if (axis) {
69465                 out = axis.calcEnds();
69466                 minY = out.from || axis.prevMin;
69467                 maxY = mmax(out.to || axis.prevMax, 0);
69468             }
69469         }
69470
69471         if (me.yField && !Ext.isNumber(minY)) {
69472             axis = Ext.create('Ext.chart.axis.Axis', {
69473                 chart: chart,
69474                 fields: [].concat(me.yField)
69475             });
69476             out = axis.calcEnds();
69477             minY = out.from || axis.prevMin;
69478             maxY = mmax(out.to || axis.prevMax, 0);
69479         }
69480
69481         if (!Ext.isNumber(minY)) {
69482             minY = 0;
69483         }
69484         if (!Ext.isNumber(maxY)) {
69485             maxY = 0;
69486         }
69487
69488         store.each(function(record, i) {
69489             xValue = record.get(me.xField);
69490             yValue = [];
69491             if (typeof xValue != 'number') {
69492                 xValue = i;
69493             }
69494             xValues.push(xValue);
69495             acumY = 0;
69496             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
69497                 areaElem = record.get(areas[areaIndex]);
69498                 if (typeof areaElem == 'number') {
69499                     minY = mmin(minY, areaElem);
69500                     yValue.push(areaElem);
69501                     acumY += areaElem;
69502                 }
69503             }
69504             minX = mmin(minX, xValue);
69505             maxX = mmax(maxX, xValue);
69506             maxY = mmax(maxY, acumY);
69507             yValues.push(yValue);
69508         }, me);
69509
69510         xScale = bbox.width / ((maxX - minX) || 1);
69511         yScale = bbox.height / ((maxY - minY) || 1);
69512
69513         ln = xValues.length;
69514         if ((ln > bbox.width) && me.areas) {
69515             sumValues = me.shrink(xValues, yValues, bbox.width);
69516             xValues = sumValues.x;
69517             yValues = sumValues.y;
69518         }
69519
69520         return {
69521             bbox: bbox,
69522             minX: minX,
69523             minY: minY,
69524             xValues: xValues,
69525             yValues: yValues,
69526             xScale: xScale,
69527             yScale: yScale,
69528             areasLen: areasLen
69529         };
69530     },
69531
69532     // @private Build an array of paths for the chart
69533     getPaths: function() {
69534         var me = this,
69535             chart = me.chart,
69536             store = chart.getChartStore(),
69537             first = true,
69538             bounds = me.getBounds(),
69539             bbox = bounds.bbox,
69540             items = me.items = [],
69541             componentPaths = [],
69542             componentPath,
69543             paths = [],
69544             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
69545
69546         ln = bounds.xValues.length;
69547         // Start the path
69548         for (i = 0; i < ln; i++) {
69549             xValue = bounds.xValues[i];
69550             yValue = bounds.yValues[i];
69551             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
69552             acumY = 0;
69553             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
69554                 // Excluded series
69555                 if (me.__excludes[areaIndex]) {
69556                     continue;
69557                 }
69558                 if (!componentPaths[areaIndex]) {
69559                     componentPaths[areaIndex] = [];
69560                 }
69561                 areaElem = yValue[areaIndex];
69562                 acumY += areaElem;
69563                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
69564                 if (!paths[areaIndex]) {
69565                     paths[areaIndex] = ['M', x, y];
69566                     componentPaths[areaIndex].push(['L', x, y]);
69567                 } else {
69568                     paths[areaIndex].push('L', x, y);
69569                     componentPaths[areaIndex].push(['L', x, y]);
69570                 }
69571                 if (!items[areaIndex]) {
69572                     items[areaIndex] = {
69573                         pointsUp: [],
69574                         pointsDown: [],
69575                         series: me
69576                     };
69577                 }
69578                 items[areaIndex].pointsUp.push([x, y]);
69579             }
69580         }
69581
69582         // Close the paths
69583         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
69584             // Excluded series
69585             if (me.__excludes[areaIndex]) {
69586                 continue;
69587             }
69588             path = paths[areaIndex];
69589             // Close bottom path to the axis
69590             if (areaIndex == 0 || first) {
69591                 first = false;
69592                 path.push('L', x, bbox.y + bbox.height,
69593                           'L', bbox.x, bbox.y + bbox.height,
69594                           'Z');
69595             }
69596             // Close other paths to the one before them
69597             else {
69598                 componentPath = componentPaths[prevAreaIndex];
69599                 componentPath.reverse();
69600                 path.push('L', x, componentPath[0][2]);
69601                 for (i = 0; i < ln; i++) {
69602                     path.push(componentPath[i][0],
69603                               componentPath[i][1],
69604                               componentPath[i][2]);
69605                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
69606                 }
69607                 path.push('L', bbox.x, path[2], 'Z');
69608             }
69609             prevAreaIndex = areaIndex;
69610         }
69611         return {
69612             paths: paths,
69613             areasLen: bounds.areasLen
69614         };
69615     },
69616
69617     /**
69618      * Draws the series for the current chart.
69619      */
69620     drawSeries: function() {
69621         var me = this,
69622             chart = me.chart,
69623             store = chart.getChartStore(),
69624             surface = chart.surface,
69625             animate = chart.animate,
69626             group = me.group,
69627             endLineStyle = Ext.apply(me.seriesStyle, me.style),
69628             colorArrayStyle = me.colorArrayStyle,
69629             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
69630             areaIndex, areaElem, paths, path, rendererAttributes;
69631
69632         me.unHighlightItem();
69633         me.cleanHighlights();
69634
69635         if (!store || !store.getCount()) {
69636             return;
69637         }
69638
69639         paths = me.getPaths();
69640
69641         if (!me.areas) {
69642             me.areas = [];
69643         }
69644
69645         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
69646             // Excluded series
69647             if (me.__excludes[areaIndex]) {
69648                 continue;
69649             }
69650             if (!me.areas[areaIndex]) {
69651                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
69652                     type: 'path',
69653                     group: group,
69654                     // 'clip-rect': me.clipBox,
69655                     path: paths.paths[areaIndex],
69656                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
69657                     fill: colorArrayStyle[areaIndex % colorArrayLength]
69658                 }, endLineStyle || {}));
69659             }
69660             areaElem = me.areas[areaIndex];
69661             path = paths.paths[areaIndex];
69662             if (animate) {
69663                 //Add renderer to line. There is not a unique record associated with this.
69664                 rendererAttributes = me.renderer(areaElem, false, {
69665                     path: path,
69666                     // 'clip-rect': me.clipBox,
69667                     fill: colorArrayStyle[areaIndex % colorArrayLength],
69668                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
69669                 }, areaIndex, store);
69670                 //fill should not be used here but when drawing the special fill path object
69671                 me.animation = me.onAnimate(areaElem, {
69672                     to: rendererAttributes
69673                 });
69674             } else {
69675                 rendererAttributes = me.renderer(areaElem, false, {
69676                     path: path,
69677                     // 'clip-rect': me.clipBox,
69678                     hidden: false,
69679                     fill: colorArrayStyle[areaIndex % colorArrayLength],
69680                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
69681                 }, areaIndex, store);
69682                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
69683             }
69684         }
69685         me.renderLabels();
69686         me.renderCallouts();
69687     },
69688
69689     // @private
69690     onAnimate: function(sprite, attr) {
69691         sprite.show();
69692         return this.callParent(arguments);
69693     },
69694
69695     // @private
69696     onCreateLabel: function(storeItem, item, i, display) {
69697         var me = this,
69698             group = me.labelsGroup,
69699             config = me.label,
69700             bbox = me.bbox,
69701             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
69702
69703         return me.chart.surface.add(Ext.apply({
69704             'type': 'text',
69705             'text-anchor': 'middle',
69706             'group': group,
69707             'x': item.point[0],
69708             'y': bbox.y + bbox.height / 2
69709         }, endLabelStyle || {}));
69710     },
69711
69712     // @private
69713     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
69714         var me = this,
69715             chart = me.chart,
69716             resizing = chart.resizing,
69717             config = me.label,
69718             format = config.renderer,
69719             field = config.field,
69720             bbox = me.bbox,
69721             x = item.point[0],
69722             y = item.point[1],
69723             bb, width, height;
69724
69725         label.setAttributes({
69726             text: format(storeItem.get(field[index])),
69727             hidden: true
69728         }, true);
69729
69730         bb = label.getBBox();
69731         width = bb.width / 2;
69732         height = bb.height / 2;
69733
69734         x = x - width < bbox.x? bbox.x + width : x;
69735         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
69736         y = y - height < bbox.y? bbox.y + height : y;
69737         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
69738
69739         if (me.chart.animate && !me.chart.resizing) {
69740             label.show(true);
69741             me.onAnimate(label, {
69742                 to: {
69743                     x: x,
69744                     y: y
69745                 }
69746             });
69747         } else {
69748             label.setAttributes({
69749                 x: x,
69750                 y: y
69751             }, true);
69752             if (resizing) {
69753                 me.animation.on('afteranimate', function() {
69754                     label.show(true);
69755                 });
69756             } else {
69757                 label.show(true);
69758             }
69759         }
69760     },
69761
69762     // @private
69763     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
69764         var me = this,
69765             chart = me.chart,
69766             surface = chart.surface,
69767             resizing = chart.resizing,
69768             config = me.callouts,
69769             items = me.items,
69770             prev = (i == 0) ? false : items[i -1].point,
69771             next = (i == items.length -1) ? false : items[i +1].point,
69772             cur = item.point,
69773             dir, norm, normal, a, aprev, anext,
69774             bbox = callout.label.getBBox(),
69775             offsetFromViz = 30,
69776             offsetToSide = 10,
69777             offsetBox = 3,
69778             boxx, boxy, boxw, boxh,
69779             p, clipRect = me.clipRect,
69780             x, y;
69781
69782         //get the right two points
69783         if (!prev) {
69784             prev = cur;
69785         }
69786         if (!next) {
69787             next = cur;
69788         }
69789         a = (next[1] - prev[1]) / (next[0] - prev[0]);
69790         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
69791         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
69792
69793         norm = Math.sqrt(1 + a * a);
69794         dir = [1 / norm, a / norm];
69795         normal = [-dir[1], dir[0]];
69796
69797         //keep the label always on the outer part of the "elbow"
69798         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
69799             normal[0] *= -1;
69800             normal[1] *= -1;
69801         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
69802             normal[0] *= -1;
69803             normal[1] *= -1;
69804         }
69805
69806         //position
69807         x = cur[0] + normal[0] * offsetFromViz;
69808         y = cur[1] + normal[1] * offsetFromViz;
69809
69810         //box position and dimensions
69811         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
69812         boxy = y - bbox.height /2 - offsetBox;
69813         boxw = bbox.width + 2 * offsetBox;
69814         boxh = bbox.height + 2 * offsetBox;
69815
69816         //now check if we're out of bounds and invert the normal vector correspondingly
69817         //this may add new overlaps between labels (but labels won't be out of bounds).
69818         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
69819             normal[0] *= -1;
69820         }
69821         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
69822             normal[1] *= -1;
69823         }
69824
69825         //update positions
69826         x = cur[0] + normal[0] * offsetFromViz;
69827         y = cur[1] + normal[1] * offsetFromViz;
69828
69829         //update box position and dimensions
69830         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
69831         boxy = y - bbox.height /2 - offsetBox;
69832         boxw = bbox.width + 2 * offsetBox;
69833         boxh = bbox.height + 2 * offsetBox;
69834
69835         //set the line from the middle of the pie to the box.
69836         callout.lines.setAttributes({
69837             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
69838         }, true);
69839         //set box position
69840         callout.box.setAttributes({
69841             x: boxx,
69842             y: boxy,
69843             width: boxw,
69844             height: boxh
69845         }, true);
69846         //set text position
69847         callout.label.setAttributes({
69848             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
69849             y: y
69850         }, true);
69851         for (p in callout) {
69852             callout[p].show(true);
69853         }
69854     },
69855
69856     isItemInPoint: function(x, y, item, i) {
69857         var me = this,
69858             pointsUp = item.pointsUp,
69859             pointsDown = item.pointsDown,
69860             abs = Math.abs,
69861             dist = Infinity, p, pln, point;
69862
69863         for (p = 0, pln = pointsUp.length; p < pln; p++) {
69864             point = [pointsUp[p][0], pointsUp[p][1]];
69865             if (dist > abs(x - point[0])) {
69866                 dist = abs(x - point[0]);
69867             } else {
69868                 point = pointsUp[p -1];
69869                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
69870                     item.storeIndex = p -1;
69871                     item.storeField = me.yField[i];
69872                     item.storeItem = me.chart.store.getAt(p -1);
69873                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
69874                     return true;
69875                 } else {
69876                     break;
69877                 }
69878             }
69879         }
69880         return false;
69881     },
69882
69883     /**
69884      * Highlight this entire series.
69885      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
69886      */
69887     highlightSeries: function() {
69888         var area, to, fillColor;
69889         if (this._index !== undefined) {
69890             area = this.areas[this._index];
69891             if (area.__highlightAnim) {
69892                 area.__highlightAnim.paused = true;
69893             }
69894             area.__highlighted = true;
69895             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
69896             area.__prevFill = area.__prevFill || area.attr.fill;
69897             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
69898             fillColor = Ext.draw.Color.fromString(area.__prevFill);
69899             to = {
69900                 lineWidth: (area.__prevLineWidth || 0) + 2
69901             };
69902             if (fillColor) {
69903                 to.fill = fillColor.getLighter(0.2).toString();
69904             }
69905             else {
69906                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
69907             }
69908             if (this.chart.animate) {
69909                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
69910                     target: area,
69911                     to: to
69912                 }, this.chart.animate));
69913             }
69914             else {
69915                 area.setAttributes(to, true);
69916             }
69917         }
69918     },
69919
69920     /**
69921      * UnHighlight this entire series.
69922      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
69923      */
69924     unHighlightSeries: function() {
69925         var area;
69926         if (this._index !== undefined) {
69927             area = this.areas[this._index];
69928             if (area.__highlightAnim) {
69929                 area.__highlightAnim.paused = true;
69930             }
69931             if (area.__highlighted) {
69932                 area.__highlighted = false;
69933                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
69934                     target: area,
69935                     to: {
69936                         fill: area.__prevFill,
69937                         opacity: area.__prevOpacity,
69938                         lineWidth: area.__prevLineWidth
69939                     }
69940                 });
69941             }
69942         }
69943     },
69944
69945     /**
69946      * Highlight the specified item. If no item is provided the whole series will be highlighted.
69947      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
69948      */
69949     highlightItem: function(item) {
69950         var me = this,
69951             points, path;
69952         if (!item) {
69953             this.highlightSeries();
69954             return;
69955         }
69956         points = item._points;
69957         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
69958                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
69959         me.highlightSprite.setAttributes({
69960             path: path,
69961             hidden: false
69962         }, true);
69963     },
69964
69965     /**
69966      * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
69967      * @param {Object} item Info about the item; same format as returned by #getItemForPoint
69968      */
69969     unHighlightItem: function(item) {
69970         if (!item) {
69971             this.unHighlightSeries();
69972         }
69973
69974         if (this.highlightSprite) {
69975             this.highlightSprite.hide(true);
69976         }
69977     },
69978
69979     // @private
69980     hideAll: function() {
69981         if (!isNaN(this._index)) {
69982             this.__excludes[this._index] = true;
69983             this.areas[this._index].hide(true);
69984             this.drawSeries();
69985         }
69986     },
69987
69988     // @private
69989     showAll: function() {
69990         if (!isNaN(this._index)) {
69991             this.__excludes[this._index] = false;
69992             this.areas[this._index].show(true);
69993             this.drawSeries();
69994         }
69995     },
69996
69997     /**
69998      * Returns the color of the series (to be displayed as color for the series legend item).
69999      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70000      */
70001     getLegendColor: function(index) {
70002         var me = this;
70003         return me.colorArrayStyle[index % me.colorArrayStyle.length];
70004     }
70005 });
70006 /**
70007  * @class Ext.chart.series.Area
70008  * @extends Ext.chart.series.Cartesian
70009  *
70010  * Creates a Stacked Area Chart. The stacked area chart is useful when displaying multiple aggregated layers of information.
70011  * As with all other series, the Area Series must be appended in the *series* Chart array configuration. See the Chart
70012  * documentation for more information. A typical configuration object for the area series could be:
70013  *
70014  *     @example
70015  *     var store = Ext.create('Ext.data.JsonStore', {
70016  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
70017  *         data: [
70018  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
70019  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
70020  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
70021  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
70022  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
70023  *         ]
70024  *     });
70025  *
70026  *     Ext.create('Ext.chart.Chart', {
70027  *         renderTo: Ext.getBody(),
70028  *         width: 500,
70029  *         height: 300,
70030  *         store: store,
70031  *         axes: [
70032  *             {
70033  *                 type: 'Numeric',
70034  *                 grid: true,
70035  *                 position: 'left',
70036  *                 fields: ['data1', 'data2', 'data3', 'data4', 'data5'],
70037  *                 title: 'Sample Values',
70038  *                 grid: {
70039  *                     odd: {
70040  *                         opacity: 1,
70041  *                         fill: '#ddd',
70042  *                         stroke: '#bbb',
70043  *                         'stroke-width': 1
70044  *                     }
70045  *                 },
70046  *                 minimum: 0,
70047  *                 adjustMinimumByMajorUnit: 0
70048  *             },
70049  *             {
70050  *                 type: 'Category',
70051  *                 position: 'bottom',
70052  *                 fields: ['name'],
70053  *                 title: 'Sample Metrics',
70054  *                 grid: true,
70055  *                 label: {
70056  *                     rotate: {
70057  *                         degrees: 315
70058  *                     }
70059  *                 }
70060  *             }
70061  *         ],
70062  *         series: [{
70063  *             type: 'area',
70064  *             highlight: false,
70065  *             axis: 'left',
70066  *             xField: 'name',
70067  *             yField: ['data1', 'data2', 'data3', 'data4', 'data5'],
70068  *             style: {
70069  *                 opacity: 0.93
70070  *             }
70071  *         }]
70072  *     });
70073  *
70074  * In this configuration we set `area` as the type for the series, set highlighting options to true for highlighting elements on hover,
70075  * 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,
70076  * and as yFields (aggregated layers) seven data fields from the same store. Then we override some theming styles by adding some opacity
70077  * to the style object.
70078  *
70079  * @xtype area
70080  */
70081 Ext.define('Ext.chart.series.Area', {
70082
70083     /* Begin Definitions */
70084
70085     extend: 'Ext.chart.series.Cartesian',
70086
70087     alias: 'series.area',
70088
70089     requires: ['Ext.chart.axis.Axis', 'Ext.draw.Color', 'Ext.fx.Anim'],
70090
70091     /* End Definitions */
70092
70093     type: 'area',
70094
70095     // @private Area charts are alyways stacked
70096     stacked: true,
70097
70098     /**
70099      * @cfg {Object} style
70100      * Append styling properties to this object for it to override theme properties.
70101      */
70102     style: {},
70103
70104     constructor: function(config) {
70105         this.callParent(arguments);
70106         var me = this,
70107             surface = me.chart.surface,
70108             i, l;
70109         Ext.apply(me, config, {
70110             __excludes: [],
70111             highlightCfg: {
70112                 lineWidth: 3,
70113                 stroke: '#55c',
70114                 opacity: 0.8,
70115                 color: '#f00'
70116             }
70117         });
70118         if (me.highlight) {
70119             me.highlightSprite = surface.add({
70120                 type: 'path',
70121                 path: ['M', 0, 0],
70122                 zIndex: 1000,
70123                 opacity: 0.3,
70124                 lineWidth: 5,
70125                 hidden: true,
70126                 stroke: '#444'
70127             });
70128         }
70129         me.group = surface.getGroup(me.seriesId);
70130     },
70131
70132     // @private Shrinks dataSets down to a smaller size
70133     shrink: function(xValues, yValues, size) {
70134         var len = xValues.length,
70135             ratio = Math.floor(len / size),
70136             i, j,
70137             xSum = 0,
70138             yCompLen = this.areas.length,
70139             ySum = [],
70140             xRes = [],
70141             yRes = [];
70142         //initialize array
70143         for (j = 0; j < yCompLen; ++j) {
70144             ySum[j] = 0;
70145         }
70146         for (i = 0; i < len; ++i) {
70147             xSum += xValues[i];
70148             for (j = 0; j < yCompLen; ++j) {
70149                 ySum[j] += yValues[i][j];
70150             }
70151             if (i % ratio == 0) {
70152                 //push averages
70153                 xRes.push(xSum/ratio);
70154                 for (j = 0; j < yCompLen; ++j) {
70155                     ySum[j] /= ratio;
70156                 }
70157                 yRes.push(ySum);
70158                 //reset sum accumulators
70159                 xSum = 0;
70160                 for (j = 0, ySum = []; j < yCompLen; ++j) {
70161                     ySum[j] = 0;
70162                 }
70163             }
70164         }
70165         return {
70166             x: xRes,
70167             y: yRes
70168         };
70169     },
70170
70171     // @private Get chart and data boundaries
70172     getBounds: function() {
70173         var me = this,
70174             chart = me.chart,
70175             store = chart.getChartStore(),
70176             areas = [].concat(me.yField),
70177             areasLen = areas.length,
70178             xValues = [],
70179             yValues = [],
70180             infinity = Infinity,
70181             minX = infinity,
70182             minY = infinity,
70183             maxX = -infinity,
70184             maxY = -infinity,
70185             math = Math,
70186             mmin = math.min,
70187             mmax = math.max,
70188             bbox, xScale, yScale, xValue, yValue, areaIndex, acumY, ln, sumValues, clipBox, areaElem;
70189
70190         me.setBBox();
70191         bbox = me.bbox;
70192
70193         // Run through the axis
70194         if (me.axis) {
70195             axis = chart.axes.get(me.axis);
70196             if (axis) {
70197                 out = axis.calcEnds();
70198                 minY = out.from || axis.prevMin;
70199                 maxY = mmax(out.to || axis.prevMax, 0);
70200             }
70201         }
70202
70203         if (me.yField && !Ext.isNumber(minY)) {
70204             axis = Ext.create('Ext.chart.axis.Axis', {
70205                 chart: chart,
70206                 fields: [].concat(me.yField)
70207             });
70208             out = axis.calcEnds();
70209             minY = out.from || axis.prevMin;
70210             maxY = mmax(out.to || axis.prevMax, 0);
70211         }
70212
70213         if (!Ext.isNumber(minY)) {
70214             minY = 0;
70215         }
70216         if (!Ext.isNumber(maxY)) {
70217             maxY = 0;
70218         }
70219
70220         store.each(function(record, i) {
70221             xValue = record.get(me.xField);
70222             yValue = [];
70223             if (typeof xValue != 'number') {
70224                 xValue = i;
70225             }
70226             xValues.push(xValue);
70227             acumY = 0;
70228             for (areaIndex = 0; areaIndex < areasLen; areaIndex++) {
70229                 areaElem = record.get(areas[areaIndex]);
70230                 if (typeof areaElem == 'number') {
70231                     minY = mmin(minY, areaElem);
70232                     yValue.push(areaElem);
70233                     acumY += areaElem;
70234                 }
70235             }
70236             minX = mmin(minX, xValue);
70237             maxX = mmax(maxX, xValue);
70238             maxY = mmax(maxY, acumY);
70239             yValues.push(yValue);
70240         }, me);
70241
70242         xScale = bbox.width / ((maxX - minX) || 1);
70243         yScale = bbox.height / ((maxY - minY) || 1);
70244
70245         ln = xValues.length;
70246         if ((ln > bbox.width) && me.areas) {
70247             sumValues = me.shrink(xValues, yValues, bbox.width);
70248             xValues = sumValues.x;
70249             yValues = sumValues.y;
70250         }
70251
70252         return {
70253             bbox: bbox,
70254             minX: minX,
70255             minY: minY,
70256             xValues: xValues,
70257             yValues: yValues,
70258             xScale: xScale,
70259             yScale: yScale,
70260             areasLen: areasLen
70261         };
70262     },
70263
70264     // @private Build an array of paths for the chart
70265     getPaths: function() {
70266         var me = this,
70267             chart = me.chart,
70268             store = chart.getChartStore(),
70269             first = true,
70270             bounds = me.getBounds(),
70271             bbox = bounds.bbox,
70272             items = me.items = [],
70273             componentPaths = [],
70274             componentPath,
70275             paths = [],
70276             i, ln, x, y, xValue, yValue, acumY, areaIndex, prevAreaIndex, areaElem, path;
70277
70278         ln = bounds.xValues.length;
70279         // Start the path
70280         for (i = 0; i < ln; i++) {
70281             xValue = bounds.xValues[i];
70282             yValue = bounds.yValues[i];
70283             x = bbox.x + (xValue - bounds.minX) * bounds.xScale;
70284             acumY = 0;
70285             for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
70286                 // Excluded series
70287                 if (me.__excludes[areaIndex]) {
70288                     continue;
70289                 }
70290                 if (!componentPaths[areaIndex]) {
70291                     componentPaths[areaIndex] = [];
70292                 }
70293                 areaElem = yValue[areaIndex];
70294                 acumY += areaElem;
70295                 y = bbox.y + bbox.height - (acumY - bounds.minY) * bounds.yScale;
70296                 if (!paths[areaIndex]) {
70297                     paths[areaIndex] = ['M', x, y];
70298                     componentPaths[areaIndex].push(['L', x, y]);
70299                 } else {
70300                     paths[areaIndex].push('L', x, y);
70301                     componentPaths[areaIndex].push(['L', x, y]);
70302                 }
70303                 if (!items[areaIndex]) {
70304                     items[areaIndex] = {
70305                         pointsUp: [],
70306                         pointsDown: [],
70307                         series: me
70308                     };
70309                 }
70310                 items[areaIndex].pointsUp.push([x, y]);
70311             }
70312         }
70313
70314         // Close the paths
70315         for (areaIndex = 0; areaIndex < bounds.areasLen; areaIndex++) {
70316             // Excluded series
70317             if (me.__excludes[areaIndex]) {
70318                 continue;
70319             }
70320             path = paths[areaIndex];
70321             // Close bottom path to the axis
70322             if (areaIndex == 0 || first) {
70323                 first = false;
70324                 path.push('L', x, bbox.y + bbox.height,
70325                           'L', bbox.x, bbox.y + bbox.height,
70326                           'Z');
70327             }
70328             // Close other paths to the one before them
70329             else {
70330                 componentPath = componentPaths[prevAreaIndex];
70331                 componentPath.reverse();
70332                 path.push('L', x, componentPath[0][2]);
70333                 for (i = 0; i < ln; i++) {
70334                     path.push(componentPath[i][0],
70335                               componentPath[i][1],
70336                               componentPath[i][2]);
70337                     items[areaIndex].pointsDown[ln -i -1] = [componentPath[i][1], componentPath[i][2]];
70338                 }
70339                 path.push('L', bbox.x, path[2], 'Z');
70340             }
70341             prevAreaIndex = areaIndex;
70342         }
70343         return {
70344             paths: paths,
70345             areasLen: bounds.areasLen
70346         };
70347     },
70348
70349     /**
70350      * Draws the series for the current chart.
70351      */
70352     drawSeries: function() {
70353         var me = this,
70354             chart = me.chart,
70355             store = chart.getChartStore(),
70356             surface = chart.surface,
70357             animate = chart.animate,
70358             group = me.group,
70359             endLineStyle = Ext.apply(me.seriesStyle, me.style),
70360             colorArrayStyle = me.colorArrayStyle,
70361             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
70362             areaIndex, areaElem, paths, path, rendererAttributes;
70363
70364         me.unHighlightItem();
70365         me.cleanHighlights();
70366
70367         if (!store || !store.getCount()) {
70368             return;
70369         }
70370
70371         paths = me.getPaths();
70372
70373         if (!me.areas) {
70374             me.areas = [];
70375         }
70376
70377         for (areaIndex = 0; areaIndex < paths.areasLen; areaIndex++) {
70378             // Excluded series
70379             if (me.__excludes[areaIndex]) {
70380                 continue;
70381             }
70382             if (!me.areas[areaIndex]) {
70383                 me.items[areaIndex].sprite = me.areas[areaIndex] = surface.add(Ext.apply({}, {
70384                     type: 'path',
70385                     group: group,
70386                     // 'clip-rect': me.clipBox,
70387                     path: paths.paths[areaIndex],
70388                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength],
70389                     fill: colorArrayStyle[areaIndex % colorArrayLength]
70390                 }, endLineStyle || {}));
70391             }
70392             areaElem = me.areas[areaIndex];
70393             path = paths.paths[areaIndex];
70394             if (animate) {
70395                 //Add renderer to line. There is not a unique record associated with this.
70396                 rendererAttributes = me.renderer(areaElem, false, {
70397                     path: path,
70398                     // 'clip-rect': me.clipBox,
70399                     fill: colorArrayStyle[areaIndex % colorArrayLength],
70400                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
70401                 }, areaIndex, store);
70402                 //fill should not be used here but when drawing the special fill path object
70403                 me.animation = me.onAnimate(areaElem, {
70404                     to: rendererAttributes
70405                 });
70406             } else {
70407                 rendererAttributes = me.renderer(areaElem, false, {
70408                     path: path,
70409                     // 'clip-rect': me.clipBox,
70410                     hidden: false,
70411                     fill: colorArrayStyle[areaIndex % colorArrayLength],
70412                     stroke: endLineStyle.stroke || colorArrayStyle[areaIndex % colorArrayLength]
70413                 }, areaIndex, store);
70414                 me.areas[areaIndex].setAttributes(rendererAttributes, true);
70415             }
70416         }
70417         me.renderLabels();
70418         me.renderCallouts();
70419     },
70420
70421     // @private
70422     onAnimate: function(sprite, attr) {
70423         sprite.show();
70424         return this.callParent(arguments);
70425     },
70426
70427     // @private
70428     onCreateLabel: function(storeItem, item, i, display) {
70429         var me = this,
70430             group = me.labelsGroup,
70431             config = me.label,
70432             bbox = me.bbox,
70433             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
70434
70435         return me.chart.surface.add(Ext.apply({
70436             'type': 'text',
70437             'text-anchor': 'middle',
70438             'group': group,
70439             'x': item.point[0],
70440             'y': bbox.y + bbox.height / 2
70441         }, endLabelStyle || {}));
70442     },
70443
70444     // @private
70445     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
70446         var me = this,
70447             chart = me.chart,
70448             resizing = chart.resizing,
70449             config = me.label,
70450             format = config.renderer,
70451             field = config.field,
70452             bbox = me.bbox,
70453             x = item.point[0],
70454             y = item.point[1],
70455             bb, width, height;
70456
70457         label.setAttributes({
70458             text: format(storeItem.get(field[index])),
70459             hidden: true
70460         }, true);
70461
70462         bb = label.getBBox();
70463         width = bb.width / 2;
70464         height = bb.height / 2;
70465
70466         x = x - width < bbox.x? bbox.x + width : x;
70467         x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
70468         y = y - height < bbox.y? bbox.y + height : y;
70469         y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
70470
70471         if (me.chart.animate && !me.chart.resizing) {
70472             label.show(true);
70473             me.onAnimate(label, {
70474                 to: {
70475                     x: x,
70476                     y: y
70477                 }
70478             });
70479         } else {
70480             label.setAttributes({
70481                 x: x,
70482                 y: y
70483             }, true);
70484             if (resizing) {
70485                 me.animation.on('afteranimate', function() {
70486                     label.show(true);
70487                 });
70488             } else {
70489                 label.show(true);
70490             }
70491         }
70492     },
70493
70494     // @private
70495     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
70496         var me = this,
70497             chart = me.chart,
70498             surface = chart.surface,
70499             resizing = chart.resizing,
70500             config = me.callouts,
70501             items = me.items,
70502             prev = (i == 0) ? false : items[i -1].point,
70503             next = (i == items.length -1) ? false : items[i +1].point,
70504             cur = item.point,
70505             dir, norm, normal, a, aprev, anext,
70506             bbox = callout.label.getBBox(),
70507             offsetFromViz = 30,
70508             offsetToSide = 10,
70509             offsetBox = 3,
70510             boxx, boxy, boxw, boxh,
70511             p, clipRect = me.clipRect,
70512             x, y;
70513
70514         //get the right two points
70515         if (!prev) {
70516             prev = cur;
70517         }
70518         if (!next) {
70519             next = cur;
70520         }
70521         a = (next[1] - prev[1]) / (next[0] - prev[0]);
70522         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
70523         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
70524
70525         norm = Math.sqrt(1 + a * a);
70526         dir = [1 / norm, a / norm];
70527         normal = [-dir[1], dir[0]];
70528
70529         //keep the label always on the outer part of the "elbow"
70530         if (aprev > 0 && anext < 0 && normal[1] < 0 || aprev < 0 && anext > 0 && normal[1] > 0) {
70531             normal[0] *= -1;
70532             normal[1] *= -1;
70533         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0 || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
70534             normal[0] *= -1;
70535             normal[1] *= -1;
70536         }
70537
70538         //position
70539         x = cur[0] + normal[0] * offsetFromViz;
70540         y = cur[1] + normal[1] * offsetFromViz;
70541
70542         //box position and dimensions
70543         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
70544         boxy = y - bbox.height /2 - offsetBox;
70545         boxw = bbox.width + 2 * offsetBox;
70546         boxh = bbox.height + 2 * offsetBox;
70547
70548         //now check if we're out of bounds and invert the normal vector correspondingly
70549         //this may add new overlaps between labels (but labels won't be out of bounds).
70550         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
70551             normal[0] *= -1;
70552         }
70553         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
70554             normal[1] *= -1;
70555         }
70556
70557         //update positions
70558         x = cur[0] + normal[0] * offsetFromViz;
70559         y = cur[1] + normal[1] * offsetFromViz;
70560
70561         //update box position and dimensions
70562         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
70563         boxy = y - bbox.height /2 - offsetBox;
70564         boxw = bbox.width + 2 * offsetBox;
70565         boxh = bbox.height + 2 * offsetBox;
70566
70567         //set the line from the middle of the pie to the box.
70568         callout.lines.setAttributes({
70569             path: ["M", cur[0], cur[1], "L", x, y, "Z"]
70570         }, true);
70571         //set box position
70572         callout.box.setAttributes({
70573             x: boxx,
70574             y: boxy,
70575             width: boxw,
70576             height: boxh
70577         }, true);
70578         //set text position
70579         callout.label.setAttributes({
70580             x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
70581             y: y
70582         }, true);
70583         for (p in callout) {
70584             callout[p].show(true);
70585         }
70586     },
70587
70588     isItemInPoint: function(x, y, item, i) {
70589         var me = this,
70590             pointsUp = item.pointsUp,
70591             pointsDown = item.pointsDown,
70592             abs = Math.abs,
70593             dist = Infinity, p, pln, point;
70594
70595         for (p = 0, pln = pointsUp.length; p < pln; p++) {
70596             point = [pointsUp[p][0], pointsUp[p][1]];
70597             if (dist > abs(x - point[0])) {
70598                 dist = abs(x - point[0]);
70599             } else {
70600                 point = pointsUp[p -1];
70601                 if (y >= point[1] && (!pointsDown.length || y <= (pointsDown[p -1][1]))) {
70602                     item.storeIndex = p -1;
70603                     item.storeField = me.yField[i];
70604                     item.storeItem = me.chart.store.getAt(p -1);
70605                     item._points = pointsDown.length? [point, pointsDown[p -1]] : [point];
70606                     return true;
70607                 } else {
70608                     break;
70609                 }
70610             }
70611         }
70612         return false;
70613     },
70614
70615     /**
70616      * Highlight this entire series.
70617      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
70618      */
70619     highlightSeries: function() {
70620         var area, to, fillColor;
70621         if (this._index !== undefined) {
70622             area = this.areas[this._index];
70623             if (area.__highlightAnim) {
70624                 area.__highlightAnim.paused = true;
70625             }
70626             area.__highlighted = true;
70627             area.__prevOpacity = area.__prevOpacity || area.attr.opacity || 1;
70628             area.__prevFill = area.__prevFill || area.attr.fill;
70629             area.__prevLineWidth = area.__prevLineWidth || area.attr.lineWidth;
70630             fillColor = Ext.draw.Color.fromString(area.__prevFill);
70631             to = {
70632                 lineWidth: (area.__prevLineWidth || 0) + 2
70633             };
70634             if (fillColor) {
70635                 to.fill = fillColor.getLighter(0.2).toString();
70636             }
70637             else {
70638                 to.opacity = Math.max(area.__prevOpacity - 0.3, 0);
70639             }
70640             if (this.chart.animate) {
70641                 area.__highlightAnim = Ext.create('Ext.fx.Anim', Ext.apply({
70642                     target: area,
70643                     to: to
70644                 }, this.chart.animate));
70645             }
70646             else {
70647                 area.setAttributes(to, true);
70648             }
70649         }
70650     },
70651
70652     /**
70653      * UnHighlight this entire series.
70654      * @param {Object} item Info about the item; same format as returned by #getItemForPoint.
70655      */
70656     unHighlightSeries: function() {
70657         var area;
70658         if (this._index !== undefined) {
70659             area = this.areas[this._index];
70660             if (area.__highlightAnim) {
70661                 area.__highlightAnim.paused = true;
70662             }
70663             if (area.__highlighted) {
70664                 area.__highlighted = false;
70665                 area.__highlightAnim = Ext.create('Ext.fx.Anim', {
70666                     target: area,
70667                     to: {
70668                         fill: area.__prevFill,
70669                         opacity: area.__prevOpacity,
70670                         lineWidth: area.__prevLineWidth
70671                     }
70672                 });
70673             }
70674         }
70675     },
70676
70677     /**
70678      * Highlight the specified item. If no item is provided the whole series will be highlighted.
70679      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70680      */
70681     highlightItem: function(item) {
70682         var me = this,
70683             points, path;
70684         if (!item) {
70685             this.highlightSeries();
70686             return;
70687         }
70688         points = item._points;
70689         path = points.length == 2? ['M', points[0][0], points[0][1], 'L', points[1][0], points[1][1]]
70690                 : ['M', points[0][0], points[0][1], 'L', points[0][0], me.bbox.y + me.bbox.height];
70691         me.highlightSprite.setAttributes({
70692             path: path,
70693             hidden: false
70694         }, true);
70695     },
70696
70697     /**
70698      * un-highlights the specified item. If no item is provided it will un-highlight the entire series.
70699      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70700      */
70701     unHighlightItem: function(item) {
70702         if (!item) {
70703             this.unHighlightSeries();
70704         }
70705
70706         if (this.highlightSprite) {
70707             this.highlightSprite.hide(true);
70708         }
70709     },
70710
70711     // @private
70712     hideAll: function() {
70713         if (!isNaN(this._index)) {
70714             this.__excludes[this._index] = true;
70715             this.areas[this._index].hide(true);
70716             this.drawSeries();
70717         }
70718     },
70719
70720     // @private
70721     showAll: function() {
70722         if (!isNaN(this._index)) {
70723             this.__excludes[this._index] = false;
70724             this.areas[this._index].show(true);
70725             this.drawSeries();
70726         }
70727     },
70728
70729     /**
70730      * Returns the color of the series (to be displayed as color for the series legend item).
70731      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
70732      */
70733     getLegendColor: function(index) {
70734         var me = this;
70735         return me.colorArrayStyle[index % me.colorArrayStyle.length];
70736     }
70737 });
70738
70739 /**
70740  * Creates a Bar Chart. A Bar Chart is a useful visualization technique to display quantitative information for
70741  * different categories that can show some progression (or regression) in the dataset. As with all other series, the Bar
70742  * Series must be appended in the *series* Chart array configuration. See the Chart documentation for more information.
70743  * A typical configuration object for the bar series could be:
70744  *
70745  *     @example
70746  *     var store = Ext.create('Ext.data.JsonStore', {
70747  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
70748  *         data: [
70749  *             { 'name': 'metric one',   'data1':10, 'data2':12, 'data3':14, 'data4':8,  'data5':13 },
70750  *             { 'name': 'metric two',   'data1':7,  'data2':8,  'data3':16, 'data4':10, 'data5':3  },
70751  *             { 'name': 'metric three', 'data1':5,  'data2':2,  'data3':14, 'data4':12, 'data5':7  },
70752  *             { 'name': 'metric four',  'data1':2,  'data2':14, 'data3':6,  'data4':1,  'data5':23 },
70753  *             { 'name': 'metric five',  'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33 }
70754  *         ]
70755  *     });
70756  *
70757  *     Ext.create('Ext.chart.Chart', {
70758  *         renderTo: Ext.getBody(),
70759  *         width: 500,
70760  *         height: 300,
70761  *         animate: true,
70762  *         store: store,
70763  *         axes: [{
70764  *             type: 'Numeric',
70765  *             position: 'bottom',
70766  *             fields: ['data1'],
70767  *             label: {
70768  *                 renderer: Ext.util.Format.numberRenderer('0,0')
70769  *             },
70770  *             title: 'Sample Values',
70771  *             grid: true,
70772  *             minimum: 0
70773  *         }, {
70774  *             type: 'Category',
70775  *             position: 'left',
70776  *             fields: ['name'],
70777  *             title: 'Sample Metrics'
70778  *         }],
70779  *         series: [{
70780  *             type: 'bar',
70781  *             axis: 'bottom',
70782  *             highlight: true,
70783  *             tips: {
70784  *               trackMouse: true,
70785  *               width: 140,
70786  *               height: 28,
70787  *               renderer: function(storeItem, item) {
70788  *                 this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' views');
70789  *               }
70790  *             },
70791  *             label: {
70792  *               display: 'insideEnd',
70793  *                 field: 'data1',
70794  *                 renderer: Ext.util.Format.numberRenderer('0'),
70795  *                 orientation: 'horizontal',
70796  *                 color: '#333',
70797  *                 'text-anchor': 'middle'
70798  *             },
70799  *             xField: 'name',
70800  *             yField: ['data1']
70801  *         }]
70802  *     });
70803  *
70804  * In this configuration we set `bar` as the series type, bind the values of the bar to the bottom axis and set the
70805  * xField or category field to the `name` parameter of the store. We also set `highlight` to true which enables smooth
70806  * animations when bars are hovered. We also set some configuration for the bar labels to be displayed inside the bar,
70807  * to display the information found in the `data1` property of each element store, to render a formated text with the
70808  * `Ext.util.Format` we pass in, to have an `horizontal` orientation (as opposed to a vertical one) and we also set
70809  * other styles like `color`, `text-anchor`, etc.
70810  */
70811 Ext.define('Ext.chart.series.Bar', {
70812
70813     /* Begin Definitions */
70814
70815     extend: 'Ext.chart.series.Cartesian',
70816
70817     alternateClassName: ['Ext.chart.BarSeries', 'Ext.chart.BarChart', 'Ext.chart.StackedBarChart'],
70818
70819     requires: ['Ext.chart.axis.Axis', 'Ext.fx.Anim'],
70820
70821     /* End Definitions */
70822
70823     type: 'bar',
70824
70825     alias: 'series.bar',
70826     /**
70827      * @cfg {Boolean} column Whether to set the visualization as column chart or horizontal bar chart.
70828      */
70829     column: false,
70830
70831     /**
70832      * @cfg style Style properties that will override the theming series styles.
70833      */
70834     style: {},
70835
70836     /**
70837      * @cfg {Number} gutter The gutter space between single bars, as a percentage of the bar width
70838      */
70839     gutter: 38.2,
70840
70841     /**
70842      * @cfg {Number} groupGutter The gutter space between groups of bars, as a percentage of the bar width
70843      */
70844     groupGutter: 38.2,
70845
70846     /**
70847      * @cfg {Number} xPadding Padding between the left/right axes and the bars
70848      */
70849     xPadding: 0,
70850
70851     /**
70852      * @cfg {Number} yPadding Padding between the top/bottom axes and the bars
70853      */
70854     yPadding: 10,
70855
70856     constructor: function(config) {
70857         this.callParent(arguments);
70858         var me = this,
70859             surface = me.chart.surface,
70860             shadow = me.chart.shadow,
70861             i, l;
70862         Ext.apply(me, config, {
70863             highlightCfg: {
70864                 lineWidth: 3,
70865                 stroke: '#55c',
70866                 opacity: 0.8,
70867                 color: '#f00'
70868             },
70869
70870             shadowAttributes: [{
70871                 "stroke-width": 6,
70872                 "stroke-opacity": 0.05,
70873                 stroke: 'rgb(200, 200, 200)',
70874                 translate: {
70875                     x: 1.2,
70876                     y: 1.2
70877                 }
70878             }, {
70879                 "stroke-width": 4,
70880                 "stroke-opacity": 0.1,
70881                 stroke: 'rgb(150, 150, 150)',
70882                 translate: {
70883                     x: 0.9,
70884                     y: 0.9
70885                 }
70886             }, {
70887                 "stroke-width": 2,
70888                 "stroke-opacity": 0.15,
70889                 stroke: 'rgb(100, 100, 100)',
70890                 translate: {
70891                     x: 0.6,
70892                     y: 0.6
70893                 }
70894             }]
70895         });
70896         me.group = surface.getGroup(me.seriesId + '-bars');
70897         if (shadow) {
70898             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
70899                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
70900             }
70901         }
70902     },
70903
70904     // @private sets the bar girth.
70905     getBarGirth: function() {
70906         var me = this,
70907             store = me.chart.getChartStore(),
70908             column = me.column,
70909             ln = store.getCount(),
70910             gutter = me.gutter / 100;
70911
70912         return (me.chart.chartBBox[column ? 'width' : 'height'] - me[column ? 'xPadding' : 'yPadding'] * 2) / (ln * (gutter + 1) - gutter);
70913     },
70914
70915     // @private returns the gutters.
70916     getGutters: function() {
70917         var me = this,
70918             column = me.column,
70919             gutter = Math.ceil(me[column ? 'xPadding' : 'yPadding'] + me.getBarGirth() / 2);
70920         return me.column ? [gutter, 0] : [0, gutter];
70921     },
70922
70923     // @private Get chart and data boundaries
70924     getBounds: function() {
70925         var me = this,
70926             chart = me.chart,
70927             store = chart.getChartStore(),
70928             bars = [].concat(me.yField),
70929             barsLen = bars.length,
70930             groupBarsLen = barsLen,
70931             groupGutter = me.groupGutter / 100,
70932             column = me.column,
70933             xPadding = me.xPadding,
70934             yPadding = me.yPadding,
70935             stacked = me.stacked,
70936             barWidth = me.getBarGirth(),
70937             math = Math,
70938             mmax = math.max,
70939             mabs = math.abs,
70940             groupBarWidth, bbox, minY, maxY, axis, out,
70941             scale, zero, total, rec, j, plus, minus;
70942
70943         me.setBBox(true);
70944         bbox = me.bbox;
70945
70946         //Skip excluded series
70947         if (me.__excludes) {
70948             for (j = 0, total = me.__excludes.length; j < total; j++) {
70949                 if (me.__excludes[j]) {
70950                     groupBarsLen--;
70951                 }
70952             }
70953         }
70954
70955         if (me.axis) {
70956             axis = chart.axes.get(me.axis);
70957             if (axis) {
70958                 out = axis.calcEnds();
70959                 minY = out.from;
70960                 maxY = out.to;
70961             }
70962         }
70963
70964         if (me.yField && !Ext.isNumber(minY)) {
70965             axis = Ext.create('Ext.chart.axis.Axis', {
70966                 chart: chart,
70967                 fields: [].concat(me.yField)
70968             });
70969             out = axis.calcEnds();
70970             minY = out.from;
70971             maxY = out.to;
70972         }
70973
70974         if (!Ext.isNumber(minY)) {
70975             minY = 0;
70976         }
70977         if (!Ext.isNumber(maxY)) {
70978             maxY = 0;
70979         }
70980         scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (maxY - minY);
70981         groupBarWidth = barWidth / ((stacked ? 1 : groupBarsLen) * (groupGutter + 1) - groupGutter);
70982         zero = (column) ? bbox.y + bbox.height - yPadding : bbox.x + xPadding;
70983
70984         if (stacked) {
70985             total = [[], []];
70986             store.each(function(record, i) {
70987                 total[0][i] = total[0][i] || 0;
70988                 total[1][i] = total[1][i] || 0;
70989                 for (j = 0; j < barsLen; j++) {
70990                     if (me.__excludes && me.__excludes[j]) {
70991                         continue;
70992                     }
70993                     rec = record.get(bars[j]);
70994                     total[+(rec > 0)][i] += mabs(rec);
70995                 }
70996             });
70997             total[+(maxY > 0)].push(mabs(maxY));
70998             total[+(minY > 0)].push(mabs(minY));
70999             minus = mmax.apply(math, total[0]);
71000             plus = mmax.apply(math, total[1]);
71001             scale = (column ? bbox.height - yPadding * 2 : bbox.width - xPadding * 2) / (plus + minus);
71002             zero = zero + minus * scale * (column ? -1 : 1);
71003         }
71004         else if (minY / maxY < 0) {
71005             zero = zero - minY * scale * (column ? -1 : 1);
71006         }
71007         return {
71008             bars: bars,
71009             bbox: bbox,
71010             barsLen: barsLen,
71011             groupBarsLen: groupBarsLen,
71012             barWidth: barWidth,
71013             groupBarWidth: groupBarWidth,
71014             scale: scale,
71015             zero: zero,
71016             xPadding: xPadding,
71017             yPadding: yPadding,
71018             signed: minY / maxY < 0,
71019             minY: minY,
71020             maxY: maxY
71021         };
71022     },
71023
71024     // @private Build an array of paths for the chart
71025     getPaths: function() {
71026         var me = this,
71027             chart = me.chart,
71028             store = chart.getChartStore(),
71029             bounds = me.bounds = me.getBounds(),
71030             items = me.items = [],
71031             gutter = me.gutter / 100,
71032             groupGutter = me.groupGutter / 100,
71033             animate = chart.animate,
71034             column = me.column,
71035             group = me.group,
71036             enableShadows = chart.shadow,
71037             shadowGroups = me.shadowGroups,
71038             shadowAttributes = me.shadowAttributes,
71039             shadowGroupsLn = shadowGroups.length,
71040             bbox = bounds.bbox,
71041             xPadding = me.xPadding,
71042             yPadding = me.yPadding,
71043             stacked = me.stacked,
71044             barsLen = bounds.barsLen,
71045             colors = me.colorArrayStyle,
71046             colorLength = colors && colors.length || 0,
71047             math = Math,
71048             mmax = math.max,
71049             mmin = math.min,
71050             mabs = math.abs,
71051             j, yValue, height, totalDim, totalNegDim, bottom, top, hasShadow, barAttr, attrs, counter,
71052             shadowIndex, shadow, sprite, offset, floorY;
71053
71054         store.each(function(record, i, total) {
71055             bottom = bounds.zero;
71056             top = bounds.zero;
71057             totalDim = 0;
71058             totalNegDim = 0;
71059             hasShadow = false;
71060             for (j = 0, counter = 0; j < barsLen; j++) {
71061                 // Excluded series
71062                 if (me.__excludes && me.__excludes[j]) {
71063                     continue;
71064                 }
71065                 yValue = record.get(bounds.bars[j]);
71066                 height = Math.round((yValue - mmax(bounds.minY, 0)) * bounds.scale);
71067                 barAttr = {
71068                     fill: colors[(barsLen > 1 ? j : 0) % colorLength]
71069                 };
71070                 if (column) {
71071                     Ext.apply(barAttr, {
71072                         height: height,
71073                         width: mmax(bounds.groupBarWidth, 0),
71074                         x: (bbox.x + xPadding + i * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked),
71075                         y: bottom - height
71076                     });
71077                 }
71078                 else {
71079                     // draw in reverse order
71080                     offset = (total - 1) - i;
71081                     Ext.apply(barAttr, {
71082                         height: mmax(bounds.groupBarWidth, 0),
71083                         width: height + (bottom == bounds.zero),
71084                         x: bottom + (bottom != bounds.zero),
71085                         y: (bbox.y + yPadding + offset * bounds.barWidth * (1 + gutter) + counter * bounds.groupBarWidth * (1 + groupGutter) * !stacked + 1)
71086                     });
71087                 }
71088                 if (height < 0) {
71089                     if (column) {
71090                         barAttr.y = top;
71091                         barAttr.height = mabs(height);
71092                     } else {
71093                         barAttr.x = top + height;
71094                         barAttr.width = mabs(height);
71095                     }
71096                 }
71097                 if (stacked) {
71098                     if (height < 0) {
71099                         top += height * (column ? -1 : 1);
71100                     } else {
71101                         bottom += height * (column ? -1 : 1);
71102                     }
71103                     totalDim += mabs(height);
71104                     if (height < 0) {
71105                         totalNegDim += mabs(height);
71106                     }
71107                 }
71108                 barAttr.x = Math.floor(barAttr.x) + 1;
71109                 floorY = Math.floor(barAttr.y);
71110                 if (!Ext.isIE9 && barAttr.y > floorY) {
71111                     floorY--;
71112                 }
71113                 barAttr.y = floorY;
71114                 barAttr.width = Math.floor(barAttr.width);
71115                 barAttr.height = Math.floor(barAttr.height);
71116                 items.push({
71117                     series: me,
71118                     storeItem: record,
71119                     value: [record.get(me.xField), yValue],
71120                     attr: barAttr,
71121                     point: column ? [barAttr.x + barAttr.width / 2, yValue >= 0 ? barAttr.y : barAttr.y + barAttr.height] :
71122                                     [yValue >= 0 ? barAttr.x + barAttr.width : barAttr.x, barAttr.y + barAttr.height / 2]
71123                 });
71124                 // When resizing, reset before animating
71125                 if (animate && chart.resizing) {
71126                     attrs = column ? {
71127                         x: barAttr.x,
71128                         y: bounds.zero,
71129                         width: barAttr.width,
71130                         height: 0
71131                     } : {
71132                         x: bounds.zero,
71133                         y: barAttr.y,
71134                         width: 0,
71135                         height: barAttr.height
71136                     };
71137                     if (enableShadows && (stacked && !hasShadow || !stacked)) {
71138                         hasShadow = true;
71139                         //update shadows
71140                         for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71141                             shadow = shadowGroups[shadowIndex].getAt(stacked ? i : (i * barsLen + j));
71142                             if (shadow) {
71143                                 shadow.setAttributes(attrs, true);
71144                             }
71145                         }
71146                     }
71147                     //update sprite position and width/height
71148                     sprite = group.getAt(i * barsLen + j);
71149                     if (sprite) {
71150                         sprite.setAttributes(attrs, true);
71151                     }
71152                 }
71153                 counter++;
71154             }
71155             if (stacked && items.length) {
71156                 items[i * counter].totalDim = totalDim;
71157                 items[i * counter].totalNegDim = totalNegDim;
71158             }
71159         }, me);
71160     },
71161
71162     // @private render/setAttributes on the shadows
71163     renderShadows: function(i, barAttr, baseAttrs, bounds) {
71164         var me = this,
71165             chart = me.chart,
71166             surface = chart.surface,
71167             animate = chart.animate,
71168             stacked = me.stacked,
71169             shadowGroups = me.shadowGroups,
71170             shadowAttributes = me.shadowAttributes,
71171             shadowGroupsLn = shadowGroups.length,
71172             store = chart.getChartStore(),
71173             column = me.column,
71174             items = me.items,
71175             shadows = [],
71176             zero = bounds.zero,
71177             shadowIndex, shadowBarAttr, shadow, totalDim, totalNegDim, j, rendererAttributes;
71178
71179         if ((stacked && (i % bounds.groupBarsLen === 0)) || !stacked) {
71180             j = i / bounds.groupBarsLen;
71181             //create shadows
71182             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71183                 shadowBarAttr = Ext.apply({}, shadowAttributes[shadowIndex]);
71184                 shadow = shadowGroups[shadowIndex].getAt(stacked ? j : i);
71185                 Ext.copyTo(shadowBarAttr, barAttr, 'x,y,width,height');
71186                 if (!shadow) {
71187                     shadow = surface.add(Ext.apply({
71188                         type: 'rect',
71189                         group: shadowGroups[shadowIndex]
71190                     }, Ext.apply({}, baseAttrs, shadowBarAttr)));
71191                 }
71192                 if (stacked) {
71193                     totalDim = items[i].totalDim;
71194                     totalNegDim = items[i].totalNegDim;
71195                     if (column) {
71196                         shadowBarAttr.y = zero - totalNegDim;
71197                         shadowBarAttr.height = totalDim;
71198                     }
71199                     else {
71200                         shadowBarAttr.x = zero - totalNegDim;
71201                         shadowBarAttr.width = totalDim;
71202                     }
71203                 }
71204                 if (animate) {
71205                     if (!stacked) {
71206                         rendererAttributes = me.renderer(shadow, store.getAt(j), shadowBarAttr, i, store);
71207                         me.onAnimate(shadow, { to: rendererAttributes });
71208                     }
71209                     else {
71210                         rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: true }), i, store);
71211                         shadow.setAttributes(rendererAttributes, true);
71212                     }
71213                 }
71214                 else {
71215                     rendererAttributes = me.renderer(shadow, store.getAt(j), Ext.apply(shadowBarAttr, { hidden: false }), i, store);
71216                     shadow.setAttributes(rendererAttributes, true);
71217                 }
71218                 shadows.push(shadow);
71219             }
71220         }
71221         return shadows;
71222     },
71223
71224     /**
71225      * Draws the series for the current chart.
71226      */
71227     drawSeries: function() {
71228         var me = this,
71229             chart = me.chart,
71230             store = chart.getChartStore(),
71231             surface = chart.surface,
71232             animate = chart.animate,
71233             stacked = me.stacked,
71234             column = me.column,
71235             enableShadows = chart.shadow,
71236             shadowGroups = me.shadowGroups,
71237             shadowGroupsLn = shadowGroups.length,
71238             group = me.group,
71239             seriesStyle = me.seriesStyle,
71240             items, ln, i, j, baseAttrs, sprite, rendererAttributes, shadowIndex, shadowGroup,
71241             bounds, endSeriesStyle, barAttr, attrs, anim;
71242
71243         if (!store || !store.getCount()) {
71244             return;
71245         }
71246
71247         //fill colors are taken from the colors array.
71248         delete seriesStyle.fill;
71249         endSeriesStyle = Ext.apply(seriesStyle, this.style);
71250         me.unHighlightItem();
71251         me.cleanHighlights();
71252
71253         me.getPaths();
71254         bounds = me.bounds;
71255         items = me.items;
71256
71257         baseAttrs = column ? {
71258             y: bounds.zero,
71259             height: 0
71260         } : {
71261             x: bounds.zero,
71262             width: 0
71263         };
71264         ln = items.length;
71265         // Create new or reuse sprites and animate/display
71266         for (i = 0; i < ln; i++) {
71267             sprite = group.getAt(i);
71268             barAttr = items[i].attr;
71269
71270             if (enableShadows) {
71271                 items[i].shadows = me.renderShadows(i, barAttr, baseAttrs, bounds);
71272             }
71273
71274             // Create a new sprite if needed (no height)
71275             if (!sprite) {
71276                 attrs = Ext.apply({}, baseAttrs, barAttr);
71277                 attrs = Ext.apply(attrs, endSeriesStyle || {});
71278                 sprite = surface.add(Ext.apply({}, {
71279                     type: 'rect',
71280                     group: group
71281                 }, attrs));
71282             }
71283             if (animate) {
71284                 rendererAttributes = me.renderer(sprite, store.getAt(i), barAttr, i, store);
71285                 sprite._to = rendererAttributes;
71286                 anim = me.onAnimate(sprite, { to: Ext.apply(rendererAttributes, endSeriesStyle) });
71287                 if (enableShadows && stacked && (i % bounds.barsLen === 0)) {
71288                     j = i / bounds.barsLen;
71289                     for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71290                         anim.on('afteranimate', function() {
71291                             this.show(true);
71292                         }, shadowGroups[shadowIndex].getAt(j));
71293                     }
71294                 }
71295             }
71296             else {
71297                 rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(barAttr, { hidden: false }), i, store);
71298                 sprite.setAttributes(Ext.apply(rendererAttributes, endSeriesStyle), true);
71299             }
71300             items[i].sprite = sprite;
71301         }
71302
71303         // Hide unused sprites
71304         ln = group.getCount();
71305         for (j = i; j < ln; j++) {
71306             group.getAt(j).hide(true);
71307         }
71308         // Hide unused shadows
71309         if (enableShadows) {
71310             for (shadowIndex = 0; shadowIndex < shadowGroupsLn; shadowIndex++) {
71311                 shadowGroup = shadowGroups[shadowIndex];
71312                 ln = shadowGroup.getCount();
71313                 for (j = i; j < ln; j++) {
71314                     shadowGroup.getAt(j).hide(true);
71315                 }
71316             }
71317         }
71318         me.renderLabels();
71319     },
71320
71321     // @private handled when creating a label.
71322     onCreateLabel: function(storeItem, item, i, display) {
71323         var me = this,
71324             surface = me.chart.surface,
71325             group = me.labelsGroup,
71326             config = me.label,
71327             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle || {}),
71328             sprite;
71329         return surface.add(Ext.apply({
71330             type: 'text',
71331             group: group
71332         }, endLabelStyle || {}));
71333     },
71334
71335     // @private callback used when placing a label.
71336     onPlaceLabel: function(label, storeItem, item, i, display, animate, j, index) {
71337         // Determine the label's final position. Starts with the configured preferred value but
71338         // may get flipped from inside to outside or vice-versa depending on space.
71339         var me = this,
71340             opt = me.bounds,
71341             groupBarWidth = opt.groupBarWidth,
71342             column = me.column,
71343             chart = me.chart,
71344             chartBBox = chart.chartBBox,
71345             resizing = chart.resizing,
71346             xValue = item.value[0],
71347             yValue = item.value[1],
71348             attr = item.attr,
71349             config = me.label,
71350             rotate = config.orientation == 'vertical',
71351             field = [].concat(config.field),
71352             format = config.renderer,
71353             text = format(storeItem.get(field[index])),
71354             size = me.getLabelSize(text),
71355             width = size.width,
71356             height = size.height,
71357             zero = opt.zero,
71358             outside = 'outside',
71359             insideStart = 'insideStart',
71360             insideEnd = 'insideEnd',
71361             offsetX = 10,
71362             offsetY = 6,
71363             signed = opt.signed,
71364             x, y, finalAttr;
71365
71366         label.setAttributes({
71367             text: text
71368         });
71369
71370         label.isOutside = false;
71371         if (column) {
71372             if (display == outside) {
71373                 if (height + offsetY + attr.height > (yValue >= 0 ? zero - chartBBox.y : chartBBox.y + chartBBox.height - zero)) {
71374                     display = insideEnd;
71375                 }
71376             } else {
71377                 if (height + offsetY > attr.height) {
71378                     display = outside;
71379                     label.isOutside = true;
71380                 }
71381             }
71382             x = attr.x + groupBarWidth / 2;
71383             y = display == insideStart ?
71384                     (zero + ((height / 2 + 3) * (yValue >= 0 ? -1 : 1))) :
71385                     (yValue >= 0 ? (attr.y + ((height / 2 + 3) * (display == outside ? -1 : 1))) :
71386                                    (attr.y + attr.height + ((height / 2 + 3) * (display === outside ? 1 : -1))));
71387         }
71388         else {
71389             if (display == outside) {
71390                 if (width + offsetX + attr.width > (yValue >= 0 ? chartBBox.x + chartBBox.width - zero : zero - chartBBox.x)) {
71391                     display = insideEnd;
71392                 }
71393             }
71394             else {
71395                 if (width + offsetX > attr.width) {
71396                     display = outside;
71397                     label.isOutside = true;
71398                 }
71399             }
71400             x = display == insideStart ?
71401                 (zero + ((width / 2 + 5) * (yValue >= 0 ? 1 : -1))) :
71402                 (yValue >= 0 ? (attr.x + attr.width + ((width / 2 + 5) * (display === outside ? 1 : -1))) :
71403                 (attr.x + ((width / 2 + 5) * (display === outside ? -1 : 1))));
71404             y = attr.y + groupBarWidth / 2;
71405         }
71406         //set position
71407         finalAttr = {
71408             x: x,
71409             y: y
71410         };
71411         //rotate
71412         if (rotate) {
71413             finalAttr.rotate = {
71414                 x: x,
71415                 y: y,
71416                 degrees: 270
71417             };
71418         }
71419         //check for resizing
71420         if (animate && resizing) {
71421             if (column) {
71422                 x = attr.x + attr.width / 2;
71423                 y = zero;
71424             } else {
71425                 x = zero;
71426                 y = attr.y + attr.height / 2;
71427             }
71428             label.setAttributes({
71429                 x: x,
71430                 y: y
71431             }, true);
71432             if (rotate) {
71433                 label.setAttributes({
71434                     rotate: {
71435                         x: x,
71436                         y: y,
71437                         degrees: 270
71438                     }
71439                 }, true);
71440             }
71441         }
71442         //handle animation
71443         if (animate) {
71444             me.onAnimate(label, { to: finalAttr });
71445         }
71446         else {
71447             label.setAttributes(Ext.apply(finalAttr, {
71448                 hidden: false
71449             }), true);
71450         }
71451     },
71452
71453     /* @private
71454      * Gets the dimensions of a given bar label. Uses a single hidden sprite to avoid
71455      * changing visible sprites.
71456      * @param value
71457      */
71458     getLabelSize: function(value) {
71459         var tester = this.testerLabel,
71460             config = this.label,
71461             endLabelStyle = Ext.apply({}, config, this.seriesLabelStyle || {}),
71462             rotated = config.orientation === 'vertical',
71463             bbox, w, h,
71464             undef;
71465         if (!tester) {
71466             tester = this.testerLabel = this.chart.surface.add(Ext.apply({
71467                 type: 'text',
71468                 opacity: 0
71469             }, endLabelStyle));
71470         }
71471         tester.setAttributes({
71472             text: value
71473         }, true);
71474
71475         // Flip the width/height if rotated, as getBBox returns the pre-rotated dimensions
71476         bbox = tester.getBBox();
71477         w = bbox.width;
71478         h = bbox.height;
71479         return {
71480             width: rotated ? h : w,
71481             height: rotated ? w : h
71482         };
71483     },
71484
71485     // @private used to animate label, markers and other sprites.
71486     onAnimate: function(sprite, attr) {
71487         sprite.show();
71488         return this.callParent(arguments);
71489     },
71490
71491     isItemInPoint: function(x, y, item) {
71492         var bbox = item.sprite.getBBox();
71493         return bbox.x <= x && bbox.y <= y
71494             && (bbox.x + bbox.width) >= x
71495             && (bbox.y + bbox.height) >= y;
71496     },
71497
71498     // @private hide all markers
71499     hideAll: function() {
71500         var axes = this.chart.axes;
71501         if (!isNaN(this._index)) {
71502             if (!this.__excludes) {
71503                 this.__excludes = [];
71504             }
71505             this.__excludes[this._index] = true;
71506             this.drawSeries();
71507             axes.each(function(axis) {
71508                 axis.drawAxis();
71509             });
71510         }
71511     },
71512
71513     // @private show all markers
71514     showAll: function() {
71515         var axes = this.chart.axes;
71516         if (!isNaN(this._index)) {
71517             if (!this.__excludes) {
71518                 this.__excludes = [];
71519             }
71520             this.__excludes[this._index] = false;
71521             this.drawSeries();
71522             axes.each(function(axis) {
71523                 axis.drawAxis();
71524             });
71525         }
71526     },
71527
71528     /**
71529      * Returns a string with the color to be used for the series legend item.
71530      * @param index
71531      */
71532     getLegendColor: function(index) {
71533         var me = this,
71534             colorLength = me.colorArrayStyle.length;
71535
71536         if (me.style && me.style.fill) {
71537             return me.style.fill;
71538         } else {
71539             return me.colorArrayStyle[index % colorLength];
71540         }
71541     },
71542
71543     highlightItem: function(item) {
71544         this.callParent(arguments);
71545         this.renderLabels();
71546     },
71547
71548     unHighlightItem: function() {
71549         this.callParent(arguments);
71550         this.renderLabels();
71551     },
71552
71553     cleanHighlights: function() {
71554         this.callParent(arguments);
71555         this.renderLabels();
71556     }
71557 });
71558 /**
71559  * @class Ext.chart.series.Column
71560  * @extends Ext.chart.series.Bar
71561  *
71562  * Creates a Column Chart. Much of the methods are inherited from Bar. A Column Chart is a useful
71563  * visualization technique to display quantitative information for different categories that can
71564  * show some progression (or regression) in the data set. As with all other series, the Column Series
71565  * must be appended in the *series* Chart array configuration. See the Chart documentation for more
71566  * information. A typical configuration object for the column series could be:
71567  *
71568  *     @example
71569  *     var store = Ext.create('Ext.data.JsonStore', {
71570  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
71571  *         data: [
71572  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
71573  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
71574  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
71575  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
71576  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
71577  *         ]
71578  *     });
71579  *
71580  *     Ext.create('Ext.chart.Chart', {
71581  *         renderTo: Ext.getBody(),
71582  *         width: 500,
71583  *         height: 300,
71584  *         animate: true,
71585  *         store: store,
71586  *         axes: [
71587  *             {
71588  *                 type: 'Numeric',
71589  *                 position: 'left',
71590  *                 fields: ['data1'],
71591  *                 label: {
71592  *                     renderer: Ext.util.Format.numberRenderer('0,0')
71593  *                 },
71594  *                 title: 'Sample Values',
71595  *                 grid: true,
71596  *                 minimum: 0
71597  *             },
71598  *             {
71599  *                 type: 'Category',
71600  *                 position: 'bottom',
71601  *                 fields: ['name'],
71602  *                 title: 'Sample Metrics'
71603  *             }
71604  *         ],
71605  *         series: [
71606  *             {
71607  *                 type: 'column',
71608  *                 axis: 'left',
71609  *                 highlight: true,
71610  *                 tips: {
71611  *                   trackMouse: true,
71612  *                   width: 140,
71613  *                   height: 28,
71614  *                   renderer: function(storeItem, item) {
71615  *                     this.setTitle(storeItem.get('name') + ': ' + storeItem.get('data1') + ' $');
71616  *                   }
71617  *                 },
71618  *                 label: {
71619  *                   display: 'insideEnd',
71620  *                   'text-anchor': 'middle',
71621  *                     field: 'data1',
71622  *                     renderer: Ext.util.Format.numberRenderer('0'),
71623  *                     orientation: 'vertical',
71624  *                     color: '#333'
71625  *                 },
71626  *                 xField: 'name',
71627  *                 yField: 'data1'
71628  *             }
71629  *         ]
71630  *     });
71631  *
71632  * In this configuration we set `column` as the series type, bind the values of the bars to the bottom axis,
71633  * set `highlight` to true so that bars are smoothly highlighted when hovered and bind the `xField` or category
71634  * field to the data store `name` property and the `yField` as the data1 property of a store element.
71635  */
71636 Ext.define('Ext.chart.series.Column', {
71637
71638     /* Begin Definitions */
71639
71640     alternateClassName: ['Ext.chart.ColumnSeries', 'Ext.chart.ColumnChart', 'Ext.chart.StackedColumnChart'],
71641
71642     extend: 'Ext.chart.series.Bar',
71643
71644     /* End Definitions */
71645
71646     type: 'column',
71647     alias: 'series.column',
71648
71649     column: true,
71650
71651     /**
71652      * @cfg {Number} xPadding
71653      * Padding between the left/right axes and the bars
71654      */
71655     xPadding: 10,
71656
71657     /**
71658      * @cfg {Number} yPadding
71659      * Padding between the top/bottom axes and the bars
71660      */
71661     yPadding: 0
71662 });
71663 /**
71664  * @class Ext.chart.series.Gauge
71665  * @extends Ext.chart.series.Series
71666  * 
71667  * Creates a Gauge Chart. Gauge Charts are used to show progress in a certain variable. There are two ways of using the Gauge chart.
71668  * One is setting a store element into the Gauge and selecting the field to be used from that store. Another one is instantiating the
71669  * visualization and using the `setValue` method to adjust the value you want.
71670  *
71671  * A chart/series configuration for the Gauge visualization could look like this:
71672  * 
71673  *     {
71674  *         xtype: 'chart',
71675  *         store: store,
71676  *         axes: [{
71677  *             type: 'gauge',
71678  *             position: 'gauge',
71679  *             minimum: 0,
71680  *             maximum: 100,
71681  *             steps: 10,
71682  *             margin: -10
71683  *         }],
71684  *         series: [{
71685  *             type: 'gauge',
71686  *             field: 'data1',
71687  *             donut: false,
71688  *             colorSet: ['#F49D10', '#ddd']
71689  *         }]
71690  *     }
71691  * 
71692  * In this configuration we create a special Gauge axis to be used with the gauge visualization (describing half-circle markers), and also we're
71693  * setting a maximum, minimum and steps configuration options into the axis. The Gauge series configuration contains the store field to be bound to
71694  * the visual display and the color set to be used with the visualization.
71695  * 
71696  * @xtype gauge
71697  */
71698 Ext.define('Ext.chart.series.Gauge', {
71699
71700     /* Begin Definitions */
71701
71702     extend: 'Ext.chart.series.Series',
71703
71704     /* End Definitions */
71705
71706     type: "gauge",
71707     alias: 'series.gauge',
71708
71709     rad: Math.PI / 180,
71710
71711     /**
71712      * @cfg {Number} highlightDuration
71713      * The duration for the pie slice highlight effect.
71714      */
71715     highlightDuration: 150,
71716
71717     /**
71718      * @cfg {String} angleField (required)
71719      * The store record field name to be used for the pie angles.
71720      * The values bound to this field name must be positive real numbers.
71721      */
71722     angleField: false,
71723
71724     /**
71725      * @cfg {Boolean} needle
71726      * Use the Gauge Series as an area series or add a needle to it. Default's false.
71727      */
71728     needle: false,
71729     
71730     /**
71731      * @cfg {Boolean/Number} donut
71732      * Use the entire disk or just a fraction of it for the gauge. Default's false.
71733      */
71734     donut: false,
71735
71736     /**
71737      * @cfg {Boolean} showInLegend
71738      * Whether to add the pie chart elements as legend items. Default's false.
71739      */
71740     showInLegend: false,
71741
71742     /**
71743      * @cfg {Object} style
71744      * An object containing styles for overriding series styles from Theming.
71745      */
71746     style: {},
71747     
71748     constructor: function(config) {
71749         this.callParent(arguments);
71750         var me = this,
71751             chart = me.chart,
71752             surface = chart.surface,
71753             store = chart.store,
71754             shadow = chart.shadow, i, l, cfg;
71755         Ext.apply(me, config, {
71756             shadowAttributes: [{
71757                 "stroke-width": 6,
71758                 "stroke-opacity": 1,
71759                 stroke: 'rgb(200, 200, 200)',
71760                 translate: {
71761                     x: 1.2,
71762                     y: 2
71763                 }
71764             },
71765             {
71766                 "stroke-width": 4,
71767                 "stroke-opacity": 1,
71768                 stroke: 'rgb(150, 150, 150)',
71769                 translate: {
71770                     x: 0.9,
71771                     y: 1.5
71772                 }
71773             },
71774             {
71775                 "stroke-width": 2,
71776                 "stroke-opacity": 1,
71777                 stroke: 'rgb(100, 100, 100)',
71778                 translate: {
71779                     x: 0.6,
71780                     y: 1
71781                 }
71782             }]
71783         });
71784         me.group = surface.getGroup(me.seriesId);
71785         if (shadow) {
71786             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
71787                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
71788             }
71789         }
71790         surface.customAttributes.segment = function(opt) {
71791             return me.getSegment(opt);
71792         };
71793     },
71794     
71795     //@private updates some onbefore render parameters.
71796     initialize: function() {
71797         var me = this,
71798             store = me.chart.getChartStore();
71799         //Add yFields to be used in Legend.js
71800         me.yField = [];
71801         if (me.label.field) {
71802             store.each(function(rec) {
71803                 me.yField.push(rec.get(me.label.field));
71804             });
71805         }
71806     },
71807
71808     // @private returns an object with properties for a Slice
71809     getSegment: function(opt) {
71810         var me = this,
71811             rad = me.rad,
71812             cos = Math.cos,
71813             sin = Math.sin,
71814             abs = Math.abs,
71815             x = me.centerX,
71816             y = me.centerY,
71817             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
71818             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
71819             delta = 1e-2,
71820             r = opt.endRho - opt.startRho,
71821             startAngle = opt.startAngle,
71822             endAngle = opt.endAngle,
71823             midAngle = (startAngle + endAngle) / 2 * rad,
71824             margin = opt.margin || 0,
71825             flag = abs(endAngle - startAngle) > 180,
71826             a1 = Math.min(startAngle, endAngle) * rad,
71827             a2 = Math.max(startAngle, endAngle) * rad,
71828             singleSlice = false;
71829
71830         x += margin * cos(midAngle);
71831         y += margin * sin(midAngle);
71832
71833         x1 = x + opt.startRho * cos(a1);
71834         y1 = y + opt.startRho * sin(a1);
71835
71836         x2 = x + opt.endRho * cos(a1);
71837         y2 = y + opt.endRho * sin(a1);
71838
71839         x3 = x + opt.startRho * cos(a2);
71840         y3 = y + opt.startRho * sin(a2);
71841
71842         x4 = x + opt.endRho * cos(a2);
71843         y4 = y + opt.endRho * sin(a2);
71844
71845         if (abs(x1 - x3) <= delta && abs(y1 - y3) <= delta) {
71846             singleSlice = true;
71847         }
71848         //Solves mysterious clipping bug with IE
71849         if (singleSlice) {
71850             return {
71851                 path: [
71852                 ["M", x1, y1],
71853                 ["L", x2, y2],
71854                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
71855                 ["Z"]]
71856             };
71857         } else {
71858             return {
71859                 path: [
71860                 ["M", x1, y1],
71861                 ["L", x2, y2],
71862                 ["A", opt.endRho, opt.endRho, 0, +flag, 1, x4, y4],
71863                 ["L", x3, y3],
71864                 ["A", opt.startRho, opt.startRho, 0, +flag, 0, x1, y1],
71865                 ["Z"]]
71866             };
71867         }
71868     },
71869
71870     // @private utility function to calculate the middle point of a pie slice.
71871     calcMiddle: function(item) {
71872         var me = this,
71873             rad = me.rad,
71874             slice = item.slice,
71875             x = me.centerX,
71876             y = me.centerY,
71877             startAngle = slice.startAngle,
71878             endAngle = slice.endAngle,
71879             radius = Math.max(('rho' in slice) ? slice.rho: me.radius, me.label.minMargin),
71880             donut = +me.donut,
71881             a1 = Math.min(startAngle, endAngle) * rad,
71882             a2 = Math.max(startAngle, endAngle) * rad,
71883             midAngle = -(a1 + (a2 - a1) / 2),
71884             xm = x + (item.endRho + item.startRho) / 2 * Math.cos(midAngle),
71885             ym = y - (item.endRho + item.startRho) / 2 * Math.sin(midAngle);
71886
71887         item.middle = {
71888             x: xm,
71889             y: ym
71890         };
71891     },
71892
71893     /**
71894      * Draws the series for the current chart.
71895      */
71896     drawSeries: function() {
71897         var me = this,
71898             chart = me.chart,
71899             store = chart.getChartStore(),
71900             group = me.group,
71901             animate = me.chart.animate,
71902             axis = me.chart.axes.get(0),
71903             minimum = axis && axis.minimum || me.minimum || 0,
71904             maximum = axis && axis.maximum || me.maximum || 0,
71905             field = me.angleField || me.field || me.xField,
71906             surface = chart.surface,
71907             chartBBox = chart.chartBBox,
71908             rad = me.rad,
71909             donut = +me.donut,
71910             values = {},
71911             items = [],
71912             seriesStyle = me.seriesStyle,
71913             seriesLabelStyle = me.seriesLabelStyle,
71914             colorArrayStyle = me.colorArrayStyle,
71915             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
71916             gutterX = chart.maxGutter[0],
71917             gutterY = chart.maxGutter[1],
71918             cos = Math.cos,
71919             sin = Math.sin,
71920             rendererAttributes, centerX, centerY, slice, slices, sprite, value,
71921             item, ln, record, i, j, startAngle, endAngle, middleAngle, sliceLength, path,
71922             p, spriteOptions, bbox, splitAngle, sliceA, sliceB;
71923         
71924         Ext.apply(seriesStyle, me.style || {});
71925
71926         me.setBBox();
71927         bbox = me.bbox;
71928
71929         //override theme colors
71930         if (me.colorSet) {
71931             colorArrayStyle = me.colorSet;
71932             colorArrayLength = colorArrayStyle.length;
71933         }
71934         
71935         //if not store or store is empty then there's nothing to draw
71936         if (!store || !store.getCount()) {
71937             return;
71938         }
71939         
71940         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
71941         centerY = me.centerY = chartBBox.y + chartBBox.height;
71942         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
71943         me.slices = slices = [];
71944         me.items = items = [];
71945         
71946         if (!me.value) {
71947             record = store.getAt(0);
71948             me.value = record.get(field);
71949         }
71950         
71951         value = me.value;
71952         if (me.needle) {
71953             sliceA = {
71954                 series: me,
71955                 value: value,
71956                 startAngle: -180,
71957                 endAngle: 0,
71958                 rho: me.radius
71959             };
71960             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
71961             slices.push(sliceA);
71962         } else {
71963             splitAngle = -180 * (1 - (value - minimum) / (maximum - minimum));
71964             sliceA = {
71965                 series: me,
71966                 value: value,
71967                 startAngle: -180,
71968                 endAngle: splitAngle,
71969                 rho: me.radius
71970             };
71971             sliceB = {
71972                 series: me,
71973                 value: me.maximum - value,
71974                 startAngle: splitAngle,
71975                 endAngle: 0,
71976                 rho: me.radius
71977             };
71978             slices.push(sliceA, sliceB);
71979         }
71980         
71981         //do pie slices after.
71982         for (i = 0, ln = slices.length; i < ln; i++) {
71983             slice = slices[i];
71984             sprite = group.getAt(i);
71985             //set pie slice properties
71986             rendererAttributes = Ext.apply({
71987                 segment: {
71988                     startAngle: slice.startAngle,
71989                     endAngle: slice.endAngle,
71990                     margin: 0,
71991                     rho: slice.rho,
71992                     startRho: slice.rho * +donut / 100,
71993                     endRho: slice.rho
71994                 } 
71995             }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
71996
71997             item = Ext.apply({},
71998             rendererAttributes.segment, {
71999                 slice: slice,
72000                 series: me,
72001                 storeItem: record,
72002                 index: i
72003             });
72004             items[i] = item;
72005             // Create a new sprite if needed (no height)
72006             if (!sprite) {
72007                 spriteOptions = Ext.apply({
72008                     type: "path",
72009                     group: group
72010                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[i % colorArrayLength] } || {}));
72011                 sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
72012             }
72013             slice.sprite = slice.sprite || [];
72014             item.sprite = sprite;
72015             slice.sprite.push(sprite);
72016             if (animate) {
72017                 rendererAttributes = me.renderer(sprite, record, rendererAttributes, i, store);
72018                 sprite._to = rendererAttributes;
72019                 me.onAnimate(sprite, {
72020                     to: rendererAttributes
72021                 });
72022             } else {
72023                 rendererAttributes = me.renderer(sprite, record, Ext.apply(rendererAttributes, {
72024                     hidden: false
72025                 }), i, store);
72026                 sprite.setAttributes(rendererAttributes, true);
72027             }
72028         }
72029         
72030         if (me.needle) {
72031             splitAngle = splitAngle * Math.PI / 180;
72032             
72033             if (!me.needleSprite) {
72034                 me.needleSprite = me.chart.surface.add({
72035                     type: 'path',
72036                     path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72037                                 centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72038                            'L', centerX + me.radius * cos(splitAngle),
72039                                 centerY + -Math.abs(me.radius * sin(splitAngle))],
72040                     'stroke-width': 4,
72041                     'stroke': '#222'
72042                 });
72043             } else {
72044                 if (animate) {
72045                     me.onAnimate(me.needleSprite, {
72046                         to: {
72047                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72048                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72049                                'L', centerX + me.radius * cos(splitAngle),
72050                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
72051                         }
72052                     });
72053                 } else {
72054                     me.needleSprite.setAttributes({
72055                         type: 'path',
72056                         path: ['M', centerX + (me.radius * +donut / 100) * cos(splitAngle),
72057                                     centerY + -Math.abs((me.radius * +donut / 100) * sin(splitAngle)),
72058                                'L', centerX + me.radius * cos(splitAngle),
72059                                     centerY + -Math.abs(me.radius * sin(splitAngle))]
72060                     });
72061                 }
72062             }
72063             me.needleSprite.setAttributes({
72064                 hidden: false    
72065             }, true);
72066         }
72067         
72068         delete me.value;
72069     },
72070     
72071     /**
72072      * Sets the Gauge chart to the current specified value.
72073     */
72074     setValue: function (value) {
72075         this.value = value;
72076         this.drawSeries();
72077     },
72078
72079     // @private callback for when creating a label sprite.
72080     onCreateLabel: function(storeItem, item, i, display) {},
72081
72082     // @private callback for when placing a label sprite.
72083     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {},
72084
72085     // @private callback for when placing a callout.
72086     onPlaceCallout: function() {},
72087
72088     // @private handles sprite animation for the series.
72089     onAnimate: function(sprite, attr) {
72090         sprite.show();
72091         return this.callParent(arguments);
72092     },
72093
72094     isItemInPoint: function(x, y, item, i) {
72095         return false;
72096     },
72097     
72098     // @private shows all elements in the series.
72099     showAll: function() {
72100         if (!isNaN(this._index)) {
72101             this.__excludes[this._index] = false;
72102             this.drawSeries();
72103         }
72104     },
72105     
72106     /**
72107      * Returns the color of the series (to be displayed as color for the series legend item).
72108      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
72109      */
72110     getLegendColor: function(index) {
72111         var me = this;
72112         return me.colorArrayStyle[index % me.colorArrayStyle.length];
72113     }
72114 });
72115
72116
72117 /**
72118  * @class Ext.chart.series.Line
72119  * @extends Ext.chart.series.Cartesian
72120  *
72121  * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different
72122  * categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
72123  * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart
72124  * documentation for more information. A typical configuration object for the line series could be:
72125  *
72126  *     @example
72127  *     var store = Ext.create('Ext.data.JsonStore', {
72128  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
72129  *         data: [
72130  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
72131  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
72132  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
72133  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
72134  *             { 'name': 'metric five',  'data1': 4,  'data2': 4,  'data3': 36, 'data4': 13, 'data5': 33 }
72135  *         ]
72136  *     });
72137  *
72138  *     Ext.create('Ext.chart.Chart', {
72139  *         renderTo: Ext.getBody(),
72140  *         width: 500,
72141  *         height: 300,
72142  *         animate: true,
72143  *         store: store,
72144  *         axes: [
72145  *             {
72146  *                 type: 'Numeric',
72147  *                 position: 'left',
72148  *                 fields: ['data1', 'data2'],
72149  *                 label: {
72150  *                     renderer: Ext.util.Format.numberRenderer('0,0')
72151  *                 },
72152  *                 title: 'Sample Values',
72153  *                 grid: true,
72154  *                 minimum: 0
72155  *             },
72156  *             {
72157  *                 type: 'Category',
72158  *                 position: 'bottom',
72159  *                 fields: ['name'],
72160  *                 title: 'Sample Metrics'
72161  *             }
72162  *         ],
72163  *         series: [
72164  *             {
72165  *                 type: 'line',
72166  *                 highlight: {
72167  *                     size: 7,
72168  *                     radius: 7
72169  *                 },
72170  *                 axis: 'left',
72171  *                 xField: 'name',
72172  *                 yField: 'data1',
72173  *                 markerConfig: {
72174  *                     type: 'cross',
72175  *                     size: 4,
72176  *                     radius: 4,
72177  *                     'stroke-width': 0
72178  *                 }
72179  *             },
72180  *             {
72181  *                 type: 'line',
72182  *                 highlight: {
72183  *                     size: 7,
72184  *                     radius: 7
72185  *                 },
72186  *                 axis: 'left',
72187  *                 fill: true,
72188  *                 xField: 'name',
72189  *                 yField: 'data2',
72190  *                 markerConfig: {
72191  *                     type: 'circle',
72192  *                     size: 4,
72193  *                     radius: 4,
72194  *                     'stroke-width': 0
72195  *                 }
72196  *             }
72197  *         ]
72198  *     });
72199  *
72200  * In this configuration we're adding two series (or lines), one bound to the `data1`
72201  * property of the store and the other to `data3`. The type for both configurations is
72202  * `line`. The `xField` for both series is the same, the name propert of the store.
72203  * Both line series share the same axis, the left axis. You can set particular marker
72204  * configuration by adding properties onto the markerConfig object. Both series have
72205  * an object as highlight so that markers animate smoothly to the properties in highlight
72206  * when hovered. The second series has `fill=true` which means that the line will also
72207  * have an area below it of the same color.
72208  *
72209  * **Note:** In the series definition remember to explicitly set the axis to bind the
72210  * values of the line series to. This can be done by using the `axis` configuration property.
72211  */
72212 Ext.define('Ext.chart.series.Line', {
72213
72214     /* Begin Definitions */
72215
72216     extend: 'Ext.chart.series.Cartesian',
72217
72218     alternateClassName: ['Ext.chart.LineSeries', 'Ext.chart.LineChart'],
72219
72220     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.draw.Draw', 'Ext.fx.Anim'],
72221
72222     /* End Definitions */
72223
72224     type: 'line',
72225
72226     alias: 'series.line',
72227
72228     /**
72229      * @cfg {String} axis
72230      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
72231      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
72232      * relative scale will be used.
72233      */
72234
72235     /**
72236      * @cfg {Number} selectionTolerance
72237      * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
72238      */
72239     selectionTolerance: 20,
72240
72241     /**
72242      * @cfg {Boolean} showMarkers
72243      * Whether markers should be displayed at the data points along the line. If true,
72244      * then the {@link #markerConfig} config item will determine the markers' styling.
72245      */
72246     showMarkers: true,
72247
72248     /**
72249      * @cfg {Object} markerConfig
72250      * The display style for the markers. Only used if {@link #showMarkers} is true.
72251      * The markerConfig is a configuration object containing the same set of properties defined in
72252      * the Sprite class. For example, if we were to set red circles as markers to the line series we could
72253      * pass the object:
72254      *
72255      <pre><code>
72256         markerConfig: {
72257             type: 'circle',
72258             radius: 4,
72259             'fill': '#f00'
72260         }
72261      </code></pre>
72262
72263      */
72264     markerConfig: {},
72265
72266     /**
72267      * @cfg {Object} style
72268      * An object containing style properties for the visualization lines and fill.
72269      * These styles will override the theme styles.  The following are valid style properties:
72270      *
72271      * - `stroke` - an rgb or hex color string for the background color of the line
72272      * - `stroke-width` - the width of the stroke (integer)
72273      * - `fill` - the background fill color string (hex or rgb), only works if {@link #fill} is `true`
72274      * - `opacity` - the opacity of the line and the fill color (decimal)
72275      *
72276      * Example usage:
72277      *
72278      *     style: {
72279      *         stroke: '#00ff00',
72280      *         'stroke-width': 10,
72281      *         fill: '#80A080',
72282      *         opacity: 0.2
72283      *     }
72284      */
72285     style: {},
72286
72287     /**
72288      * @cfg {Boolean/Number} smooth
72289      * If set to `true` or a non-zero number, the line will be smoothed/rounded around its points; otherwise
72290      * straight line segments will be drawn.
72291      *
72292      * A numeric value is interpreted as a divisor of the horizontal distance between consecutive points in
72293      * the line; larger numbers result in sharper curves while smaller numbers result in smoother curves.
72294      *
72295      * If set to `true` then a default numeric value of 3 will be used. Defaults to `false`.
72296      */
72297     smooth: false,
72298
72299     /**
72300      * @private Default numeric smoothing value to be used when {@link #smooth} = true.
72301      */
72302     defaultSmoothness: 3,
72303
72304     /**
72305      * @cfg {Boolean} fill
72306      * If true, the area below the line will be filled in using the {@link #style eefill} and
72307      * {@link #style opacity} config properties. Defaults to false.
72308      */
72309     fill: false,
72310
72311     constructor: function(config) {
72312         this.callParent(arguments);
72313         var me = this,
72314             surface = me.chart.surface,
72315             shadow = me.chart.shadow,
72316             i, l;
72317         Ext.apply(me, config, {
72318             highlightCfg: {
72319                 'stroke-width': 3
72320             },
72321             shadowAttributes: [{
72322                 "stroke-width": 6,
72323                 "stroke-opacity": 0.05,
72324                 stroke: 'rgb(0, 0, 0)',
72325                 translate: {
72326                     x: 1,
72327                     y: 1
72328                 }
72329             }, {
72330                 "stroke-width": 4,
72331                 "stroke-opacity": 0.1,
72332                 stroke: 'rgb(0, 0, 0)',
72333                 translate: {
72334                     x: 1,
72335                     y: 1
72336                 }
72337             }, {
72338                 "stroke-width": 2,
72339                 "stroke-opacity": 0.15,
72340                 stroke: 'rgb(0, 0, 0)',
72341                 translate: {
72342                     x: 1,
72343                     y: 1
72344                 }
72345             }]
72346         });
72347         me.group = surface.getGroup(me.seriesId);
72348         if (me.showMarkers) {
72349             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
72350         }
72351         if (shadow) {
72352             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
72353                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
72354             }
72355         }
72356     },
72357
72358     // @private makes an average of points when there are more data points than pixels to be rendered.
72359     shrink: function(xValues, yValues, size) {
72360         // Start at the 2nd point...
72361         var len = xValues.length,
72362             ratio = Math.floor(len / size),
72363             i = 1,
72364             xSum = 0,
72365             ySum = 0,
72366             xRes = [xValues[0]],
72367             yRes = [yValues[0]];
72368
72369         for (; i < len; ++i) {
72370             xSum += xValues[i] || 0;
72371             ySum += yValues[i] || 0;
72372             if (i % ratio == 0) {
72373                 xRes.push(xSum/ratio);
72374                 yRes.push(ySum/ratio);
72375                 xSum = 0;
72376                 ySum = 0;
72377             }
72378         }
72379         return {
72380             x: xRes,
72381             y: yRes
72382         };
72383     },
72384
72385     /**
72386      * Draws the series for the current chart.
72387      */
72388     drawSeries: function() {
72389         var me = this,
72390             chart = me.chart,
72391             chartAxes = chart.axes,
72392             store = chart.getChartStore(),
72393             storeCount = store.getCount(),
72394             surface = me.chart.surface,
72395             bbox = {},
72396             group = me.group,
72397             showMarkers = me.showMarkers,
72398             markerGroup = me.markerGroup,
72399             enableShadows = chart.shadow,
72400             shadowGroups = me.shadowGroups,
72401             shadowAttributes = me.shadowAttributes,
72402             smooth = me.smooth,
72403             lnsh = shadowGroups.length,
72404             dummyPath = ["M"],
72405             path = ["M"],
72406             renderPath = ["M"],
72407             smoothPath = ["M"],
72408             markerIndex = chart.markerIndex,
72409             axes = [].concat(me.axis),
72410             shadowBarAttr,
72411             xValues = [],
72412             xValueMap = {},
72413             yValues = [],
72414             yValueMap = {},
72415             onbreak = false,
72416             storeIndices = [],
72417             markerStyle = me.markerStyle,
72418             seriesStyle = me.style,
72419             colorArrayStyle = me.colorArrayStyle,
72420             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
72421             isNumber = Ext.isNumber,
72422             seriesIdx = me.seriesIdx, 
72423             boundAxes = me.getAxesForXAndYFields(),
72424             boundXAxis = boundAxes.xAxis,
72425             boundYAxis = boundAxes.yAxis,
72426             shadows, shadow, shindex, fromPath, fill, fillPath, rendererAttributes,
72427             x, y, prevX, prevY, firstX, firstY, markerCount, i, j, ln, axis, ends, marker, markerAux, item, xValue,
72428             yValue, coords, xScale, yScale, minX, maxX, minY, maxY, line, animation, endMarkerStyle,
72429             endLineStyle, type, count, items;
72430
72431         if (me.fireEvent('beforedraw', me) === false) {
72432             return;
72433         }
72434
72435         //if store is empty or the series is excluded in the legend then there's nothing to draw.
72436         if (!storeCount || me.seriesIsHidden) {
72437             items = this.items;
72438             if (items) {
72439                 for (i = 0, ln = items.length; i < ln; ++i) {
72440                     if (items[i].sprite) {
72441                         items[i].sprite.hide(true);
72442                     }
72443                 }
72444             }
72445             return;
72446         }
72447
72448         //prepare style objects for line and markers
72449         endMarkerStyle = Ext.apply(markerStyle || {}, me.markerConfig);
72450         type = endMarkerStyle.type;
72451         delete endMarkerStyle.type;
72452         endLineStyle = seriesStyle;
72453         //if no stroke with is specified force it to 0.5 because this is
72454         //about making *lines*
72455         if (!endLineStyle['stroke-width']) {
72456             endLineStyle['stroke-width'] = 0.5;
72457         }
72458         //If we're using a time axis and we need to translate the points,
72459         //then reuse the first markers as the last markers.
72460         if (markerIndex && markerGroup && markerGroup.getCount()) {
72461             for (i = 0; i < markerIndex; i++) {
72462                 marker = markerGroup.getAt(i);
72463                 markerGroup.remove(marker);
72464                 markerGroup.add(marker);
72465                 markerAux = markerGroup.getAt(markerGroup.getCount() - 2);
72466                 marker.setAttributes({
72467                     x: 0,
72468                     y: 0,
72469                     translate: {
72470                         x: markerAux.attr.translation.x,
72471                         y: markerAux.attr.translation.y
72472                     }
72473                 }, true);
72474             }
72475         }
72476
72477         me.unHighlightItem();
72478         me.cleanHighlights();
72479
72480         me.setBBox();
72481         bbox = me.bbox;
72482         me.clipRect = [bbox.x, bbox.y, bbox.width, bbox.height];
72483         for (i = 0, ln = axes.length; i < ln; i++) {
72484             axis = chartAxes.get(axes[i]);
72485             if (axis) {
72486                 ends = axis.calcEnds();
72487                 if (axis.position == 'top' || axis.position == 'bottom') {
72488                     minX = ends.from;
72489                     maxX = ends.to;
72490                 }
72491                 else {
72492                     minY = ends.from;
72493                     maxY = ends.to;
72494                 }
72495             }
72496         }
72497         // If a field was specified without a corresponding axis, create one to get bounds
72498         //only do this for the axis where real values are bound (that's why we check for
72499         //me.axis)
72500         if (me.xField && !isNumber(minX) &&
72501             (boundXAxis == 'bottom' || boundXAxis == 'top') && 
72502             !chartAxes.get(boundXAxis)) {
72503             axis = Ext.create('Ext.chart.axis.Axis', {
72504                 chart: chart,
72505                 fields: [].concat(me.xField)
72506             }).calcEnds();
72507             minX = axis.from;
72508             maxX = axis.to;
72509         }
72510         if (me.yField && !isNumber(minY) &&
72511             (boundYAxis == 'right' || boundYAxis == 'left') &&
72512             !chartAxes.get(boundYAxis)) {
72513             axis = Ext.create('Ext.chart.axis.Axis', {
72514                 chart: chart,
72515                 fields: [].concat(me.yField)
72516             }).calcEnds();
72517             minY = axis.from;
72518             maxY = axis.to;
72519         }
72520         if (isNaN(minX)) {
72521             minX = 0;
72522             xScale = bbox.width / ((storeCount - 1) || 1);
72523         }
72524         else {
72525             xScale = bbox.width / ((maxX - minX) || (storeCount -1) || 1);
72526         }
72527
72528         if (isNaN(minY)) {
72529             minY = 0;
72530             yScale = bbox.height / ((storeCount - 1) || 1);
72531         }
72532         else {
72533             yScale = bbox.height / ((maxY - minY) || (storeCount - 1) || 1);
72534         }
72535
72536         // Extract all x and y values from the store
72537         me.eachRecord(function(record, i) {
72538             xValue = record.get(me.xField);
72539
72540             // Ensure a value
72541             if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)
72542                 //set as uniform distribution if the axis is a category axis.
72543                 || boundXAxis && chartAxes.get(boundXAxis) && chartAxes.get(boundXAxis).type == 'Category') {
72544                     if (xValue in xValueMap) {
72545                         xValue = xValueMap[xValue];
72546                     } else {
72547                         xValue = xValueMap[xValue] = i;
72548                     }
72549             }
72550
72551             // Filter out values that don't fit within the pan/zoom buffer area
72552             yValue = record.get(me.yField);
72553             //skip undefined values
72554             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
72555                 return;
72556             }
72557             // Ensure a value
72558             if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)
72559                 //set as uniform distribution if the axis is a category axis.
72560                 || boundYAxis && chartAxes.get(boundYAxis) && chartAxes.get(boundYAxis).type == 'Category') {
72561                 yValue = i;
72562             }
72563             storeIndices.push(i);
72564             xValues.push(xValue);
72565             yValues.push(yValue);
72566         });
72567
72568         ln = xValues.length;
72569         if (ln > bbox.width) {
72570             coords = me.shrink(xValues, yValues, bbox.width);
72571             xValues = coords.x;
72572             yValues = coords.y;
72573         }
72574
72575         me.items = [];
72576
72577         count = 0;
72578         ln = xValues.length;
72579         for (i = 0; i < ln; i++) {
72580             xValue = xValues[i];
72581             yValue = yValues[i];
72582             if (yValue === false) {
72583                 if (path.length == 1) {
72584                     path = [];
72585                 }
72586                 onbreak = true;
72587                 me.items.push(false);
72588                 continue;
72589             } else {
72590                 x = (bbox.x + (xValue - minX) * xScale).toFixed(2);
72591                 y = ((bbox.y + bbox.height) - (yValue - minY) * yScale).toFixed(2);
72592                 if (onbreak) {
72593                     onbreak = false;
72594                     path.push('M');
72595                 }
72596                 path = path.concat([x, y]);
72597             }
72598             if ((typeof firstY == 'undefined') && (typeof y != 'undefined')) {
72599                 firstY = y;
72600                 firstX = x;
72601             }
72602             // If this is the first line, create a dummypath to animate in from.
72603             if (!me.line || chart.resizing) {
72604                 dummyPath = dummyPath.concat([x, bbox.y + bbox.height / 2]);
72605             }
72606
72607             // When resizing, reset before animating
72608             if (chart.animate && chart.resizing && me.line) {
72609                 me.line.setAttributes({
72610                     path: dummyPath
72611                 }, true);
72612                 if (me.fillPath) {
72613                     me.fillPath.setAttributes({
72614                         path: dummyPath,
72615                         opacity: 0.2
72616                     }, true);
72617                 }
72618                 if (me.line.shadows) {
72619                     shadows = me.line.shadows;
72620                     for (j = 0, lnsh = shadows.length; j < lnsh; j++) {
72621                         shadow = shadows[j];
72622                         shadow.setAttributes({
72623                             path: dummyPath
72624                         }, true);
72625                     }
72626                 }
72627             }
72628             if (showMarkers) {
72629                 marker = markerGroup.getAt(count++);
72630                 if (!marker) {
72631                     marker = Ext.chart.Shape[type](surface, Ext.apply({
72632                         group: [group, markerGroup],
72633                         x: 0, y: 0,
72634                         translate: {
72635                             x: +(prevX || x),
72636                             y: prevY || (bbox.y + bbox.height / 2)
72637                         },
72638                         value: '"' + xValue + ', ' + yValue + '"',
72639                         zIndex: 4000
72640                     }, endMarkerStyle));
72641                     marker._to = {
72642                         translate: {
72643                             x: +x,
72644                             y: +y
72645                         }
72646                     };
72647                 } else {
72648                     marker.setAttributes({
72649                         value: '"' + xValue + ', ' + yValue + '"',
72650                         x: 0, y: 0,
72651                         hidden: false
72652                     }, true);
72653                     marker._to = {
72654                         translate: {
72655                             x: +x, 
72656                             y: +y
72657                         }
72658                     };
72659                 }
72660             }
72661             me.items.push({
72662                 series: me,
72663                 value: [xValue, yValue],
72664                 point: [x, y],
72665                 sprite: marker,
72666                 storeItem: store.getAt(storeIndices[i])
72667             });
72668             prevX = x;
72669             prevY = y;
72670         }
72671
72672         if (path.length <= 1) {
72673             //nothing to be rendered
72674             return;
72675         }
72676
72677         if (me.smooth) {
72678             smoothPath = Ext.draw.Draw.smooth(path, isNumber(smooth) ? smooth : me.defaultSmoothness);
72679         }
72680
72681         renderPath = smooth ? smoothPath : path;
72682
72683         //Correct path if we're animating timeAxis intervals
72684         if (chart.markerIndex && me.previousPath) {
72685             fromPath = me.previousPath;
72686             if (!smooth) {
72687                 Ext.Array.erase(fromPath, 1, 2);
72688             }
72689         } else {
72690             fromPath = path;
72691         }
72692
72693         // Only create a line if one doesn't exist.
72694         if (!me.line) {
72695             me.line = surface.add(Ext.apply({
72696                 type: 'path',
72697                 group: group,
72698                 path: dummyPath,
72699                 stroke: endLineStyle.stroke || endLineStyle.fill
72700             }, endLineStyle || {}));
72701
72702             if (enableShadows) {
72703                 me.line.setAttributes(Ext.apply({}, me.shadowOptions), true);
72704             }
72705
72706             //unset fill here (there's always a default fill withing the themes).
72707             me.line.setAttributes({
72708                 fill: 'none',
72709                 zIndex: 3000
72710             });
72711             if (!endLineStyle.stroke && colorArrayLength) {
72712                 me.line.setAttributes({
72713                     stroke: colorArrayStyle[seriesIdx % colorArrayLength]
72714                 }, true);
72715             }
72716             if (enableShadows) {
72717                 //create shadows
72718                 shadows = me.line.shadows = [];
72719                 for (shindex = 0; shindex < lnsh; shindex++) {
72720                     shadowBarAttr = shadowAttributes[shindex];
72721                     shadowBarAttr = Ext.apply({}, shadowBarAttr, { path: dummyPath });
72722                     shadow = surface.add(Ext.apply({}, {
72723                         type: 'path',
72724                         group: shadowGroups[shindex]
72725                     }, shadowBarAttr));
72726                     shadows.push(shadow);
72727                 }
72728             }
72729         }
72730         if (me.fill) {
72731             fillPath = renderPath.concat([
72732                 ["L", x, bbox.y + bbox.height],
72733                 ["L", firstX, bbox.y + bbox.height],
72734                 ["L", firstX, firstY]
72735             ]);
72736             if (!me.fillPath) {
72737                 me.fillPath = surface.add({
72738                     group: group,
72739                     type: 'path',
72740                     opacity: endLineStyle.opacity || 0.3,
72741                     fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
72742                     path: dummyPath
72743                 });
72744             }
72745         }
72746         markerCount = showMarkers && markerGroup.getCount();
72747         if (chart.animate) {
72748             fill = me.fill;
72749             line = me.line;
72750             //Add renderer to line. There is not unique record associated with this.
72751             rendererAttributes = me.renderer(line, false, { path: renderPath }, i, store);
72752             Ext.apply(rendererAttributes, endLineStyle || {}, {
72753                 stroke: endLineStyle.stroke || endLineStyle.fill
72754             });
72755             //fill should not be used here but when drawing the special fill path object
72756             delete rendererAttributes.fill;
72757             line.show(true);
72758             if (chart.markerIndex && me.previousPath) {
72759                 me.animation = animation = me.onAnimate(line, {
72760                     to: rendererAttributes,
72761                     from: {
72762                         path: fromPath
72763                     }
72764                 });
72765             } else {
72766                 me.animation = animation = me.onAnimate(line, {
72767                     to: rendererAttributes
72768                 });
72769             }
72770             //animate shadows
72771             if (enableShadows) {
72772                 shadows = line.shadows;
72773                 for(j = 0; j < lnsh; j++) {
72774                     shadows[j].show(true);
72775                     if (chart.markerIndex && me.previousPath) {
72776                         me.onAnimate(shadows[j], {
72777                             to: { path: renderPath },
72778                             from: { path: fromPath }
72779                         });
72780                     } else {
72781                         me.onAnimate(shadows[j], {
72782                             to: { path: renderPath }
72783                         });
72784                     }
72785                 }
72786             }
72787             //animate fill path
72788             if (fill) {
72789                 me.fillPath.show(true);
72790                 me.onAnimate(me.fillPath, {
72791                     to: Ext.apply({}, {
72792                         path: fillPath,
72793                         fill: endLineStyle.fill || colorArrayStyle[seriesIdx % colorArrayLength],
72794                         'stroke-width': 0
72795                     }, endLineStyle || {})
72796                 });
72797             }
72798             //animate markers
72799             if (showMarkers) {
72800                 count = 0;
72801                 for(i = 0; i < ln; i++) {
72802                     if (me.items[i]) {
72803                         item = markerGroup.getAt(count++);
72804                         if (item) {
72805                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
72806                             me.onAnimate(item, {
72807                                 to: Ext.apply(rendererAttributes, endMarkerStyle || {})
72808                             });
72809                             item.show(true);
72810                         }
72811                     }
72812                 }
72813                 for(; count < markerCount; count++) {
72814                     item = markerGroup.getAt(count);
72815                     item.hide(true);
72816                 }
72817 //                for(i = 0; i < (chart.markerIndex || 0)-1; i++) {
72818 //                    item = markerGroup.getAt(i);
72819 //                    item.hide(true);
72820 //                }
72821             }
72822         } else {
72823             rendererAttributes = me.renderer(me.line, false, { path: renderPath, hidden: false }, i, store);
72824             Ext.apply(rendererAttributes, endLineStyle || {}, {
72825                 stroke: endLineStyle.stroke || endLineStyle.fill
72826             });
72827             //fill should not be used here but when drawing the special fill path object
72828             delete rendererAttributes.fill;
72829             me.line.setAttributes(rendererAttributes, true);
72830             //set path for shadows
72831             if (enableShadows) {
72832                 shadows = me.line.shadows;
72833                 for(j = 0; j < lnsh; j++) {
72834                     shadows[j].setAttributes({
72835                         path: renderPath,
72836                         hidden: false
72837                     }, true);
72838                 }
72839             }
72840             if (me.fill) {
72841                 me.fillPath.setAttributes({
72842                     path: fillPath,
72843                     hidden: false
72844                 }, true);
72845             }
72846             if (showMarkers) {
72847                 count = 0;
72848                 for(i = 0; i < ln; i++) {
72849                     if (me.items[i]) {
72850                         item = markerGroup.getAt(count++);
72851                         if (item) {
72852                             rendererAttributes = me.renderer(item, store.getAt(i), item._to, i, store);
72853                             item.setAttributes(Ext.apply(endMarkerStyle || {}, rendererAttributes || {}), true);
72854                             item.show(true);
72855                         }
72856                     }
72857                 }
72858                 for(; count < markerCount; count++) {
72859                     item = markerGroup.getAt(count);
72860                     item.hide(true);
72861                 }
72862             }
72863         }
72864
72865         if (chart.markerIndex) {
72866             if (me.smooth) {
72867                 Ext.Array.erase(path, 1, 2);
72868             } else {
72869                 Ext.Array.splice(path, 1, 0, path[1], path[2]);
72870             }
72871             me.previousPath = path;
72872         }
72873         me.renderLabels();
72874         me.renderCallouts();
72875
72876         me.fireEvent('draw', me);
72877     },
72878
72879     // @private called when a label is to be created.
72880     onCreateLabel: function(storeItem, item, i, display) {
72881         var me = this,
72882             group = me.labelsGroup,
72883             config = me.label,
72884             bbox = me.bbox,
72885             endLabelStyle = Ext.apply(config, me.seriesLabelStyle);
72886
72887         return me.chart.surface.add(Ext.apply({
72888             'type': 'text',
72889             'text-anchor': 'middle',
72890             'group': group,
72891             'x': item.point[0],
72892             'y': bbox.y + bbox.height / 2
72893         }, endLabelStyle || {}));
72894     },
72895
72896     // @private called when a label is to be created.
72897     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
72898         var me = this,
72899             chart = me.chart,
72900             resizing = chart.resizing,
72901             config = me.label,
72902             format = config.renderer,
72903             field = config.field,
72904             bbox = me.bbox,
72905             x = item.point[0],
72906             y = item.point[1],
72907             radius = item.sprite.attr.radius,
72908             bb, width, height;
72909
72910         label.setAttributes({
72911             text: format(storeItem.get(field)),
72912             hidden: true
72913         }, true);
72914
72915         if (display == 'rotate') {
72916             label.setAttributes({
72917                 'text-anchor': 'start',
72918                 'rotation': {
72919                     x: x,
72920                     y: y,
72921                     degrees: -45
72922                 }
72923             }, true);
72924             //correct label position to fit into the box
72925             bb = label.getBBox();
72926             width = bb.width;
72927             height = bb.height;
72928             x = x < bbox.x? bbox.x : x;
72929             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
72930             y = (y - height < bbox.y)? bbox.y + height : y;
72931
72932         } else if (display == 'under' || display == 'over') {
72933             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
72934             bb = item.sprite.getBBox();
72935             bb.width = bb.width || (radius * 2);
72936             bb.height = bb.height || (radius * 2);
72937             y = y + (display == 'over'? -bb.height : bb.height);
72938             //correct label position to fit into the box
72939             bb = label.getBBox();
72940             width = bb.width/2;
72941             height = bb.height/2;
72942             x = x - width < bbox.x? bbox.x + width : x;
72943             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
72944             y = y - height < bbox.y? bbox.y + height : y;
72945             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
72946         }
72947
72948         if (me.chart.animate && !me.chart.resizing) {
72949             label.show(true);
72950             me.onAnimate(label, {
72951                 to: {
72952                     x: x,
72953                     y: y
72954                 }
72955             });
72956         } else {
72957             label.setAttributes({
72958                 x: x,
72959                 y: y
72960             }, true);
72961             if (resizing && me.animation) {
72962                 me.animation.on('afteranimate', function() {
72963                     label.show(true);
72964                 });
72965             } else {
72966                 label.show(true);
72967             }
72968         }
72969     },
72970
72971     //@private Overriding highlights.js highlightItem method.
72972     highlightItem: function() {
72973         var me = this;
72974         me.callParent(arguments);
72975         if (me.line && !me.highlighted) {
72976             if (!('__strokeWidth' in me.line)) {
72977                 me.line.__strokeWidth = me.line.attr['stroke-width'] || 0;
72978             }
72979             if (me.line.__anim) {
72980                 me.line.__anim.paused = true;
72981             }
72982             me.line.__anim = Ext.create('Ext.fx.Anim', {
72983                 target: me.line,
72984                 to: {
72985                     'stroke-width': me.line.__strokeWidth + 3
72986                 }
72987             });
72988             me.highlighted = true;
72989         }
72990     },
72991
72992     //@private Overriding highlights.js unHighlightItem method.
72993     unHighlightItem: function() {
72994         var me = this;
72995         me.callParent(arguments);
72996         if (me.line && me.highlighted) {
72997             me.line.__anim = Ext.create('Ext.fx.Anim', {
72998                 target: me.line,
72999                 to: {
73000                     'stroke-width': me.line.__strokeWidth
73001                 }
73002             });
73003             me.highlighted = false;
73004         }
73005     },
73006
73007     //@private called when a callout needs to be placed.
73008     onPlaceCallout : function(callout, storeItem, item, i, display, animate, index) {
73009         if (!display) {
73010             return;
73011         }
73012
73013         var me = this,
73014             chart = me.chart,
73015             surface = chart.surface,
73016             resizing = chart.resizing,
73017             config = me.callouts,
73018             items = me.items,
73019             prev = i == 0? false : items[i -1].point,
73020             next = (i == items.length -1)? false : items[i +1].point,
73021             cur = [+item.point[0], +item.point[1]],
73022             dir, norm, normal, a, aprev, anext,
73023             offsetFromViz = config.offsetFromViz || 30,
73024             offsetToSide = config.offsetToSide || 10,
73025             offsetBox = config.offsetBox || 3,
73026             boxx, boxy, boxw, boxh,
73027             p, clipRect = me.clipRect,
73028             bbox = {
73029                 width: config.styles.width || 10,
73030                 height: config.styles.height || 10
73031             },
73032             x, y;
73033
73034         //get the right two points
73035         if (!prev) {
73036             prev = cur;
73037         }
73038         if (!next) {
73039             next = cur;
73040         }
73041         a = (next[1] - prev[1]) / (next[0] - prev[0]);
73042         aprev = (cur[1] - prev[1]) / (cur[0] - prev[0]);
73043         anext = (next[1] - cur[1]) / (next[0] - cur[0]);
73044
73045         norm = Math.sqrt(1 + a * a);
73046         dir = [1 / norm, a / norm];
73047         normal = [-dir[1], dir[0]];
73048
73049         //keep the label always on the outer part of the "elbow"
73050         if (aprev > 0 && anext < 0 && normal[1] < 0
73051             || aprev < 0 && anext > 0 && normal[1] > 0) {
73052             normal[0] *= -1;
73053             normal[1] *= -1;
73054         } else if (Math.abs(aprev) < Math.abs(anext) && normal[0] < 0
73055                    || Math.abs(aprev) > Math.abs(anext) && normal[0] > 0) {
73056             normal[0] *= -1;
73057             normal[1] *= -1;
73058         }
73059         //position
73060         x = cur[0] + normal[0] * offsetFromViz;
73061         y = cur[1] + normal[1] * offsetFromViz;
73062
73063         //box position and dimensions
73064         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
73065         boxy = y - bbox.height /2 - offsetBox;
73066         boxw = bbox.width + 2 * offsetBox;
73067         boxh = bbox.height + 2 * offsetBox;
73068
73069         //now check if we're out of bounds and invert the normal vector correspondingly
73070         //this may add new overlaps between labels (but labels won't be out of bounds).
73071         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
73072             normal[0] *= -1;
73073         }
73074         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
73075             normal[1] *= -1;
73076         }
73077
73078         //update positions
73079         x = cur[0] + normal[0] * offsetFromViz;
73080         y = cur[1] + normal[1] * offsetFromViz;
73081
73082         //update box position and dimensions
73083         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
73084         boxy = y - bbox.height /2 - offsetBox;
73085         boxw = bbox.width + 2 * offsetBox;
73086         boxh = bbox.height + 2 * offsetBox;
73087
73088         if (chart.animate) {
73089             //set the line from the middle of the pie to the box.
73090             me.onAnimate(callout.lines, {
73091                 to: {
73092                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
73093                 }
73094             });
73095             //set component position
73096             if (callout.panel) {
73097                 callout.panel.setPosition(boxx, boxy, true);
73098             }
73099         }
73100         else {
73101             //set the line from the middle of the pie to the box.
73102             callout.lines.setAttributes({
73103                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
73104             }, true);
73105             //set component position
73106             if (callout.panel) {
73107                 callout.panel.setPosition(boxx, boxy);
73108             }
73109         }
73110         for (p in callout) {
73111             callout[p].show(true);
73112         }
73113     },
73114
73115     isItemInPoint: function(x, y, item, i) {
73116         var me = this,
73117             items = me.items,
73118             tolerance = me.selectionTolerance,
73119             result = null,
73120             prevItem,
73121             nextItem,
73122             prevPoint,
73123             nextPoint,
73124             ln,
73125             x1,
73126             y1,
73127             x2,
73128             y2,
73129             xIntersect,
73130             yIntersect,
73131             dist1, dist2, dist, midx, midy,
73132             sqrt = Math.sqrt, abs = Math.abs;
73133
73134         nextItem = items[i];
73135         prevItem = i && items[i - 1];
73136
73137         if (i >= ln) {
73138             prevItem = items[ln - 1];
73139         }
73140         prevPoint = prevItem && prevItem.point;
73141         nextPoint = nextItem && nextItem.point;
73142         x1 = prevItem ? prevPoint[0] : nextPoint[0] - tolerance;
73143         y1 = prevItem ? prevPoint[1] : nextPoint[1];
73144         x2 = nextItem ? nextPoint[0] : prevPoint[0] + tolerance;
73145         y2 = nextItem ? nextPoint[1] : prevPoint[1];
73146         dist1 = sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
73147         dist2 = sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
73148         dist = Math.min(dist1, dist2);
73149
73150         if (dist <= tolerance) {
73151             return dist == dist1? prevItem : nextItem;
73152         }
73153         return false;
73154     },
73155
73156     // @private toggle visibility of all series elements (markers, sprites).
73157     toggleAll: function(show) {
73158         var me = this,
73159             i, ln, shadow, shadows;
73160         if (!show) {
73161             Ext.chart.series.Cartesian.prototype.hideAll.call(me);
73162         }
73163         else {
73164             Ext.chart.series.Cartesian.prototype.showAll.call(me);
73165         }
73166         if (me.line) {
73167             me.line.setAttributes({
73168                 hidden: !show
73169             }, true);
73170             //hide shadows too
73171             if (me.line.shadows) {
73172                 for (i = 0, shadows = me.line.shadows, ln = shadows.length; i < ln; i++) {
73173                     shadow = shadows[i];
73174                     shadow.setAttributes({
73175                         hidden: !show
73176                     }, true);
73177                 }
73178             }
73179         }
73180         if (me.fillPath) {
73181             me.fillPath.setAttributes({
73182                 hidden: !show
73183             }, true);
73184         }
73185     },
73186
73187     // @private hide all series elements (markers, sprites).
73188     hideAll: function() {
73189         this.toggleAll(false);
73190     },
73191
73192     // @private hide all series elements (markers, sprites).
73193     showAll: function() {
73194         this.toggleAll(true);
73195     }
73196 });
73197
73198 /**
73199  * @class Ext.chart.series.Pie
73200  * @extends Ext.chart.series.Series
73201  *
73202  * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different
73203  * categories that also have a meaning as a whole.
73204  * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart
73205  * documentation for more information. A typical configuration object for the pie series could be:
73206  *
73207  *     @example
73208  *     var store = Ext.create('Ext.data.JsonStore', {
73209  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
73210  *         data: [
73211  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
73212  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
73213  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
73214  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
73215  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
73216  *         ]
73217  *     });
73218  *
73219  *     Ext.create('Ext.chart.Chart', {
73220  *         renderTo: Ext.getBody(),
73221  *         width: 500,
73222  *         height: 350,
73223  *         animate: true,
73224  *         store: store,
73225  *         theme: 'Base:gradients',
73226  *         series: [{
73227  *             type: 'pie',
73228  *             field: 'data1',
73229  *             showInLegend: true,
73230  *             tips: {
73231  *                 trackMouse: true,
73232  *                 width: 140,
73233  *                 height: 28,
73234  *                 renderer: function(storeItem, item) {
73235  *                     // calculate and display percentage on hover
73236  *                     var total = 0;
73237  *                     store.each(function(rec) {
73238  *                         total += rec.get('data1');
73239  *                     });
73240  *                     this.setTitle(storeItem.get('name') + ': ' + Math.round(storeItem.get('data1') / total * 100) + '%');
73241  *                 }
73242  *             },
73243  *             highlight: {
73244  *                 segment: {
73245  *                     margin: 20
73246  *                 }
73247  *             },
73248  *             label: {
73249  *                 field: 'name',
73250  *                 display: 'rotate',
73251  *                 contrast: true,
73252  *                 font: '18px Arial'
73253  *             }
73254  *         }]
73255  *     });
73256  *
73257  * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options
73258  * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item.
73259  *
73260  * 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
73261  * 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.
73262  *
73263  * 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
73264  * and size through the `font` parameter.
73265  *
73266  * @xtype pie
73267  */
73268 Ext.define('Ext.chart.series.Pie', {
73269
73270     /* Begin Definitions */
73271
73272     alternateClassName: ['Ext.chart.PieSeries', 'Ext.chart.PieChart'],
73273
73274     extend: 'Ext.chart.series.Series',
73275
73276     /* End Definitions */
73277
73278     type: "pie",
73279
73280     alias: 'series.pie',
73281
73282     rad: Math.PI / 180,
73283
73284     /**
73285      * @cfg {Number} highlightDuration
73286      * The duration for the pie slice highlight effect.
73287      */
73288     highlightDuration: 150,
73289
73290     /**
73291      * @cfg {String} angleField (required)
73292      * The store record field name to be used for the pie angles.
73293      * The values bound to this field name must be positive real numbers.
73294      */
73295     angleField: false,
73296
73297     /**
73298      * @cfg {String} lengthField
73299      * The store record field name to be used for the pie slice lengths.
73300      * The values bound to this field name must be positive real numbers.
73301      */
73302     lengthField: false,
73303
73304     /**
73305      * @cfg {Boolean/Number} donut
73306      * Whether to set the pie chart as donut chart.
73307      * Default's false. Can be set to a particular percentage to set the radius
73308      * of the donut chart.
73309      */
73310     donut: false,
73311
73312     /**
73313      * @cfg {Boolean} showInLegend
73314      * Whether to add the pie chart elements as legend items. Default's false.
73315      */
73316     showInLegend: false,
73317
73318     /**
73319      * @cfg {Array} colorSet
73320      * An array of color values which will be used, in order, as the pie slice fill colors.
73321      */
73322
73323     /**
73324      * @cfg {Object} style
73325      * An object containing styles for overriding series styles from Theming.
73326      */
73327     style: {},
73328
73329     constructor: function(config) {
73330         this.callParent(arguments);
73331         var me = this,
73332             chart = me.chart,
73333             surface = chart.surface,
73334             store = chart.store,
73335             shadow = chart.shadow, i, l, cfg;
73336         Ext.applyIf(me, {
73337             highlightCfg: {
73338                 segment: {
73339                     margin: 20
73340                 }
73341             }
73342         });
73343         Ext.apply(me, config, {
73344             shadowAttributes: [{
73345                 "stroke-width": 6,
73346                 "stroke-opacity": 1,
73347                 stroke: 'rgb(200, 200, 200)',
73348                 translate: {
73349                     x: 1.2,
73350                     y: 2
73351                 }
73352             },
73353             {
73354                 "stroke-width": 4,
73355                 "stroke-opacity": 1,
73356                 stroke: 'rgb(150, 150, 150)',
73357                 translate: {
73358                     x: 0.9,
73359                     y: 1.5
73360                 }
73361             },
73362             {
73363                 "stroke-width": 2,
73364                 "stroke-opacity": 1,
73365                 stroke: 'rgb(100, 100, 100)',
73366                 translate: {
73367                     x: 0.6,
73368                     y: 1
73369                 }
73370             }]
73371         });
73372         me.group = surface.getGroup(me.seriesId);
73373         if (shadow) {
73374             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
73375                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
73376             }
73377         }
73378         surface.customAttributes.segment = function(opt) {
73379             return me.getSegment(opt);
73380         };
73381         me.__excludes = me.__excludes || [];
73382     },
73383
73384     //@private updates some onbefore render parameters.
73385     initialize: function() {
73386         var me = this,
73387             store = me.chart.getChartStore();
73388         //Add yFields to be used in Legend.js
73389         me.yField = [];
73390         if (me.label.field) {
73391             store.each(function(rec) {
73392                 me.yField.push(rec.get(me.label.field));
73393             });
73394         }
73395     },
73396
73397     // @private returns an object with properties for a PieSlice.
73398     getSegment: function(opt) {
73399         var me = this,
73400             rad = me.rad,
73401             cos = Math.cos,
73402             sin = Math.sin,
73403             x = me.centerX,
73404             y = me.centerY,
73405             x1 = 0, x2 = 0, x3 = 0, x4 = 0,
73406             y1 = 0, y2 = 0, y3 = 0, y4 = 0,
73407             x5 = 0, y5 = 0, x6 = 0, y6 = 0,
73408             delta = 1e-2,
73409             startAngle = opt.startAngle,
73410             endAngle = opt.endAngle,
73411             midAngle = (startAngle + endAngle) / 2 * rad,
73412             margin = opt.margin || 0,
73413             a1 = Math.min(startAngle, endAngle) * rad,
73414             a2 = Math.max(startAngle, endAngle) * rad,
73415             c1 = cos(a1), s1 = sin(a1),
73416             c2 = cos(a2), s2 = sin(a2),
73417             cm = cos(midAngle), sm = sin(midAngle),
73418             flag = 0, hsqr2 = 0.7071067811865476; // sqrt(0.5)
73419
73420         if (a2 - a1 < delta) {
73421             return {path: ""};
73422         }
73423
73424         if (margin !== 0) {
73425             x += margin * cm;
73426             y += margin * sm;
73427         }
73428
73429         x2 = x + opt.endRho * c1;
73430         y2 = y + opt.endRho * s1;
73431
73432         x4 = x + opt.endRho * c2;
73433         y4 = y + opt.endRho * s2;
73434
73435         if (Math.abs(x2 - x4) + Math.abs(y2 - y4) < delta) {
73436             cm = hsqr2;
73437             sm = -hsqr2;
73438             flag = 1;
73439         }
73440
73441         x6 = x + opt.endRho * cm;
73442         y6 = y + opt.endRho * sm;
73443
73444         // TODO(bei): It seems that the canvas engine cannot render half circle command correctly on IE.
73445         // Better fix the VML engine for half circles.
73446
73447         if (opt.startRho !== 0) {
73448             x1 = x + opt.startRho * c1;
73449             y1 = y + opt.startRho * s1;
73450     
73451             x3 = x + opt.startRho * c2;
73452             y3 = y + opt.startRho * s2;
73453     
73454             x5 = x + opt.startRho * cm;
73455             y5 = y + opt.startRho * sm;
73456
73457             return {
73458                 path: [
73459                     ["M", x2, y2],
73460                     ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
73461                     ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
73462                     ["L", x3, y3],
73463                     ["A", opt.startRho, opt.startRho, 0, flag, 0, x5, y5], ["L", x5, y5],
73464                     ["A", opt.startRho, opt.startRho, 0, 0, 0, x1, y1], ["L", x1, y1],
73465                     ["Z"]
73466                 ]
73467             };
73468         } else {
73469             return {
73470                 path: [
73471                     ["M", x, y],
73472                     ["L", x2, y2],
73473                     ["A", opt.endRho, opt.endRho, 0, 0, 1, x6, y6], ["L", x6, y6],
73474                     ["A", opt.endRho, opt.endRho, 0, flag, 1, x4, y4], ["L", x4, y4],
73475                     ["L", x, y],
73476                     ["Z"]
73477                 ]
73478             };
73479         }
73480     },
73481
73482     // @private utility function to calculate the middle point of a pie slice.
73483     calcMiddle: function(item) {
73484         var me = this,
73485             rad = me.rad,
73486             slice = item.slice,
73487             x = me.centerX,
73488             y = me.centerY,
73489             startAngle = slice.startAngle,
73490             endAngle = slice.endAngle,
73491             donut = +me.donut,
73492             midAngle = -(startAngle + endAngle) * rad / 2,
73493             r = (item.endRho + item.startRho) / 2,
73494             xm = x + r * Math.cos(midAngle),
73495             ym = y - r * Math.sin(midAngle);
73496
73497         item.middle = {
73498             x: xm,
73499             y: ym
73500         };
73501     },
73502
73503     /**
73504      * Draws the series for the current chart.
73505      */
73506     drawSeries: function() {
73507         var me = this,
73508             store = me.chart.getChartStore(),
73509             group = me.group,
73510             animate = me.chart.animate,
73511             field = me.angleField || me.field || me.xField,
73512             lenField = [].concat(me.lengthField),
73513             totalLenField = 0,
73514             colors = me.colorSet,
73515             chart = me.chart,
73516             surface = chart.surface,
73517             chartBBox = chart.chartBBox,
73518             enableShadows = chart.shadow,
73519             shadowGroups = me.shadowGroups,
73520             shadowAttributes = me.shadowAttributes,
73521             lnsh = shadowGroups.length,
73522             rad = me.rad,
73523             layers = lenField.length,
73524             rhoAcum = 0,
73525             donut = +me.donut,
73526             layerTotals = [],
73527             values = {},
73528             fieldLength,
73529             items = [],
73530             passed = false,
73531             totalField = 0,
73532             maxLenField = 0,
73533             cut = 9,
73534             defcut = true,
73535             angle = 0,
73536             seriesStyle = me.seriesStyle,
73537             seriesLabelStyle = me.seriesLabelStyle,
73538             colorArrayStyle = me.colorArrayStyle,
73539             colorArrayLength = colorArrayStyle && colorArrayStyle.length || 0,
73540             gutterX = chart.maxGutter[0],
73541             gutterY = chart.maxGutter[1],
73542             abs = Math.abs,
73543             rendererAttributes,
73544             shadowGroup,
73545             shadowAttr,
73546             shadows,
73547             shadow,
73548             shindex,
73549             centerX,
73550             centerY,
73551             deltaRho,
73552             first = 0,
73553             slice,
73554             slices,
73555             sprite,
73556             value,
73557             item,
73558             lenValue,
73559             ln,
73560             record,
73561             i,
73562             j,
73563             startAngle,
73564             endAngle,
73565             middleAngle,
73566             sliceLength,
73567             path,
73568             p,
73569             spriteOptions, bbox;
73570
73571         Ext.apply(seriesStyle, me.style || {});
73572
73573         me.setBBox();
73574         bbox = me.bbox;
73575
73576         //override theme colors
73577         if (me.colorSet) {
73578             colorArrayStyle = me.colorSet;
73579             colorArrayLength = colorArrayStyle.length;
73580         }
73581
73582         //if not store or store is empty then there's nothing to draw
73583         if (!store || !store.getCount()) {
73584             return;
73585         }
73586
73587         me.unHighlightItem();
73588         me.cleanHighlights();
73589
73590         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
73591         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
73592         me.radius = Math.min(centerX - chartBBox.x, centerY - chartBBox.y);
73593         me.slices = slices = [];
73594         me.items = items = [];
73595
73596         store.each(function(record, i) {
73597             if (this.__excludes && this.__excludes[i]) {
73598                 //hidden series
73599                 return;
73600             }
73601             totalField += +record.get(field);
73602             if (lenField[0]) {
73603                 for (j = 0, totalLenField = 0; j < layers; j++) {
73604                     totalLenField += +record.get(lenField[j]);
73605                 }
73606                 layerTotals[i] = totalLenField;
73607                 maxLenField = Math.max(maxLenField, totalLenField);
73608             }
73609         }, this);
73610
73611         totalField = totalField || 1;
73612         store.each(function(record, i) {
73613             if (this.__excludes && this.__excludes[i]) {
73614                 value = 0;
73615             } else {
73616                 value = record.get(field);
73617                 if (first == 0) {
73618                     first = 1;
73619                 }
73620             }
73621
73622             // First slice
73623             if (first == 1) {
73624                 first = 2;
73625                 me.firstAngle = angle = 360 * value / totalField / 2;
73626                 for (j = 0; j < i; j++) {
73627                     slices[j].startAngle = slices[j].endAngle = me.firstAngle;
73628                 }
73629             }
73630             
73631             endAngle = angle - 360 * value / totalField;
73632             slice = {
73633                 series: me,
73634                 value: value,
73635                 startAngle: angle,
73636                 endAngle: endAngle,
73637                 storeItem: record
73638             };
73639             if (lenField[0]) {
73640                 lenValue = layerTotals[i];
73641                 slice.rho = me.radius * (lenValue / maxLenField);
73642             } else {
73643                 slice.rho = me.radius;
73644             }
73645             slices[i] = slice;
73646             angle = endAngle;
73647         }, me);
73648         //do all shadows first.
73649         if (enableShadows) {
73650             for (i = 0, ln = slices.length; i < ln; i++) {
73651                 slice = slices[i];
73652                 slice.shadowAttrs = [];
73653                 for (j = 0, rhoAcum = 0, shadows = []; j < layers; j++) {
73654                     sprite = group.getAt(i * layers + j);
73655                     deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
73656                     //set pie slice properties
73657                     rendererAttributes = {
73658                         segment: {
73659                             startAngle: slice.startAngle,
73660                             endAngle: slice.endAngle,
73661                             margin: 0,
73662                             rho: slice.rho,
73663                             startRho: rhoAcum + (deltaRho * donut / 100),
73664                             endRho: rhoAcum + deltaRho
73665                         },
73666                         hidden: !slice.value && (slice.startAngle % 360) == (slice.endAngle % 360)
73667                     };
73668                     //create shadows
73669                     for (shindex = 0, shadows = []; shindex < lnsh; shindex++) {
73670                         shadowAttr = shadowAttributes[shindex];
73671                         shadow = shadowGroups[shindex].getAt(i);
73672                         if (!shadow) {
73673                             shadow = chart.surface.add(Ext.apply({}, {
73674                                 type: 'path',
73675                                 group: shadowGroups[shindex],
73676                                 strokeLinejoin: "round"
73677                             }, rendererAttributes, shadowAttr));
73678                         }
73679                         if (animate) {
73680                             shadowAttr = me.renderer(shadow, store.getAt(i), Ext.apply({}, rendererAttributes, shadowAttr), i, store);
73681                             me.onAnimate(shadow, {
73682                                 to: shadowAttr
73683                             });
73684                         } else {
73685                             shadowAttr = me.renderer(shadow, store.getAt(i), shadowAttr, i, store);
73686                             shadow.setAttributes(shadowAttr, true);
73687                         }
73688                         shadows.push(shadow);
73689                     }
73690                     slice.shadowAttrs[j] = shadows;
73691                 }
73692             }
73693         }
73694         //do pie slices after.
73695         for (i = 0, ln = slices.length; i < ln; i++) {
73696             slice = slices[i];
73697             for (j = 0, rhoAcum = 0; j < layers; j++) {
73698                 sprite = group.getAt(i * layers + j);
73699                 deltaRho = lenField[j] ? store.getAt(i).get(lenField[j]) / layerTotals[i] * slice.rho: slice.rho;
73700                 //set pie slice properties
73701                 rendererAttributes = Ext.apply({
73702                     segment: {
73703                         startAngle: slice.startAngle,
73704                         endAngle: slice.endAngle,
73705                         margin: 0,
73706                         rho: slice.rho,
73707                         startRho: rhoAcum + (deltaRho * donut / 100),
73708                         endRho: rhoAcum + deltaRho
73709                     },
73710                     hidden: (!slice.value && (slice.startAngle % 360) == (slice.endAngle % 360))
73711                 }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
73712                 item = Ext.apply({},
73713                 rendererAttributes.segment, {
73714                     slice: slice,
73715                     series: me,
73716                     storeItem: slice.storeItem,
73717                     index: i
73718                 });
73719                 me.calcMiddle(item);
73720                 if (enableShadows) {
73721                     item.shadows = slice.shadowAttrs[j];
73722                 }
73723                 items[i] = item;
73724                 // Create a new sprite if needed (no height)
73725                 if (!sprite) {
73726                     spriteOptions = Ext.apply({
73727                         type: "path",
73728                         group: group,
73729                         middle: item.middle
73730                     }, Ext.apply(seriesStyle, colorArrayStyle && { fill: colorArrayStyle[(layers > 1? j : i) % colorArrayLength] } || {}));
73731                     sprite = surface.add(Ext.apply(spriteOptions, rendererAttributes));
73732                 }
73733                 slice.sprite = slice.sprite || [];
73734                 item.sprite = sprite;
73735                 slice.sprite.push(sprite);
73736                 slice.point = [item.middle.x, item.middle.y];
73737                 if (animate) {
73738                     rendererAttributes = me.renderer(sprite, store.getAt(i), rendererAttributes, i, store);
73739                     sprite._to = rendererAttributes;
73740                     sprite._animating = true;
73741                     me.onAnimate(sprite, {
73742                         to: rendererAttributes,
73743                         listeners: {
73744                             afteranimate: {
73745                                 fn: function() {
73746                                     this._animating = false;
73747                                 },
73748                                 scope: sprite
73749                             }
73750                         }
73751                     });
73752                 } else {
73753                     rendererAttributes = me.renderer(sprite, store.getAt(i), Ext.apply(rendererAttributes, {
73754                         hidden: false
73755                     }), i, store);
73756                     sprite.setAttributes(rendererAttributes, true);
73757                 }
73758                 rhoAcum += deltaRho;
73759             }
73760         }
73761
73762         // Hide unused bars
73763         ln = group.getCount();
73764         for (i = 0; i < ln; i++) {
73765             if (!slices[(i / layers) >> 0] && group.getAt(i)) {
73766                 group.getAt(i).hide(true);
73767             }
73768         }
73769         if (enableShadows) {
73770             lnsh = shadowGroups.length;
73771             for (shindex = 0; shindex < ln; shindex++) {
73772                 if (!slices[(shindex / layers) >> 0]) {
73773                     for (j = 0; j < lnsh; j++) {
73774                         if (shadowGroups[j].getAt(shindex)) {
73775                             shadowGroups[j].getAt(shindex).hide(true);
73776                         }
73777                     }
73778                 }
73779             }
73780         }
73781         me.renderLabels();
73782         me.renderCallouts();
73783     },
73784
73785     // @private callback for when creating a label sprite.
73786     onCreateLabel: function(storeItem, item, i, display) {
73787         var me = this,
73788             group = me.labelsGroup,
73789             config = me.label,
73790             centerX = me.centerX,
73791             centerY = me.centerY,
73792             middle = item.middle,
73793             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config || {});
73794
73795         return me.chart.surface.add(Ext.apply({
73796             'type': 'text',
73797             'text-anchor': 'middle',
73798             'group': group,
73799             'x': middle.x,
73800             'y': middle.y
73801         }, endLabelStyle));
73802     },
73803
73804     // @private callback for when placing a label sprite.
73805     onPlaceLabel: function(label, storeItem, item, i, display, animate, index) {
73806         var me = this,
73807             chart = me.chart,
73808             resizing = chart.resizing,
73809             config = me.label,
73810             format = config.renderer,
73811             field = [].concat(config.field),
73812             centerX = me.centerX,
73813             centerY = me.centerY,
73814             middle = item.middle,
73815             opt = {
73816                 x: middle.x,
73817                 y: middle.y
73818             },
73819             x = middle.x - centerX,
73820             y = middle.y - centerY,
73821             from = {},
73822             rho = 1,
73823             theta = Math.atan2(y, x || 1),
73824             dg = theta * 180 / Math.PI,
73825             prevDg;
73826         if (this.__excludes && this.__excludes[i]) {
73827             opt.hidden = true;
73828         }
73829         function fixAngle(a) {
73830             if (a < 0) {
73831                 a += 360;
73832             }
73833             return a % 360;
73834         }
73835
73836         label.setAttributes({
73837             text: format(storeItem.get(field[index]))
73838         }, true);
73839
73840         switch (display) {
73841         case 'outside':
73842             rho = Math.sqrt(x * x + y * y) * 2;
73843             //update positions
73844             opt.x = rho * Math.cos(theta) + centerX;
73845             opt.y = rho * Math.sin(theta) + centerY;
73846             break;
73847
73848         case 'rotate':
73849             dg = fixAngle(dg);
73850             dg = (dg > 90 && dg < 270) ? dg + 180: dg;
73851
73852             prevDg = label.attr.rotation.degrees;
73853             if (prevDg != null && Math.abs(prevDg - dg) > 180) {
73854                 if (dg > prevDg) {
73855                     dg -= 360;
73856                 } else {
73857                     dg += 360;
73858                 }
73859                 dg = dg % 360;
73860             } else {
73861                 dg = fixAngle(dg);
73862             }
73863             //update rotation angle
73864             opt.rotate = {
73865                 degrees: dg,
73866                 x: opt.x,
73867                 y: opt.y
73868             };
73869             break;
73870
73871         default:
73872             break;
73873         }
73874         //ensure the object has zero translation
73875         opt.translate = {
73876             x: 0, y: 0
73877         };
73878         if (animate && !resizing && (display != 'rotate' || prevDg != null)) {
73879             me.onAnimate(label, {
73880                 to: opt
73881             });
73882         } else {
73883             label.setAttributes(opt, true);
73884         }
73885         label._from = from;
73886     },
73887
73888     // @private callback for when placing a callout sprite.
73889     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
73890         var me = this,
73891             chart = me.chart,
73892             resizing = chart.resizing,
73893             config = me.callouts,
73894             centerX = me.centerX,
73895             centerY = me.centerY,
73896             middle = item.middle,
73897             opt = {
73898                 x: middle.x,
73899                 y: middle.y
73900             },
73901             x = middle.x - centerX,
73902             y = middle.y - centerY,
73903             rho = 1,
73904             rhoCenter,
73905             theta = Math.atan2(y, x || 1),
73906             bbox = callout.label.getBBox(),
73907             offsetFromViz = 20,
73908             offsetToSide = 10,
73909             offsetBox = 10,
73910             p;
73911
73912         //should be able to config this.
73913         rho = item.endRho + offsetFromViz;
73914         rhoCenter = (item.endRho + item.startRho) / 2 + (item.endRho - item.startRho) / 3;
73915         //update positions
73916         opt.x = rho * Math.cos(theta) + centerX;
73917         opt.y = rho * Math.sin(theta) + centerY;
73918
73919         x = rhoCenter * Math.cos(theta);
73920         y = rhoCenter * Math.sin(theta);
73921
73922         if (chart.animate) {
73923             //set the line from the middle of the pie to the box.
73924             me.onAnimate(callout.lines, {
73925                 to: {
73926                     path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
73927                 }
73928             });
73929             //set box position
73930             me.onAnimate(callout.box, {
73931                 to: {
73932                     x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
73933                     y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
73934                     width: bbox.width + 2 * offsetBox,
73935                     height: bbox.height + 2 * offsetBox
73936                 }
73937             });
73938             //set text position
73939             me.onAnimate(callout.label, {
73940                 to: {
73941                     x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
73942                     y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
73943                 }
73944             });
73945         } else {
73946             //set the line from the middle of the pie to the box.
73947             callout.lines.setAttributes({
73948                 path: ["M", x + centerX, y + centerY, "L", opt.x, opt.y, "Z", "M", opt.x, opt.y, "l", x > 0 ? offsetToSide: -offsetToSide, 0, "z"]
73949             },
73950             true);
73951             //set box position
73952             callout.box.setAttributes({
73953                 x: opt.x + (x > 0 ? offsetToSide: -(offsetToSide + bbox.width + 2 * offsetBox)),
73954                 y: opt.y + (y > 0 ? ( - bbox.height - offsetBox / 2) : ( - bbox.height - offsetBox / 2)),
73955                 width: bbox.width + 2 * offsetBox,
73956                 height: bbox.height + 2 * offsetBox
73957             },
73958             true);
73959             //set text position
73960             callout.label.setAttributes({
73961                 x: opt.x + (x > 0 ? (offsetToSide + offsetBox) : -(offsetToSide + bbox.width + offsetBox)),
73962                 y: opt.y + (y > 0 ? -bbox.height / 4: -bbox.height / 4)
73963             },
73964             true);
73965         }
73966         for (p in callout) {
73967             callout[p].show(true);
73968         }
73969     },
73970
73971     // @private handles sprite animation for the series.
73972     onAnimate: function(sprite, attr) {
73973         sprite.show();
73974         return this.callParent(arguments);
73975     },
73976
73977     isItemInPoint: function(x, y, item, i) {
73978         var me = this,
73979             cx = me.centerX,
73980             cy = me.centerY,
73981             abs = Math.abs,
73982             dx = abs(x - cx),
73983             dy = abs(y - cy),
73984             startAngle = item.startAngle,
73985             endAngle = item.endAngle,
73986             rho = Math.sqrt(dx * dx + dy * dy),
73987             angle = Math.atan2(y - cy, x - cx) / me.rad;
73988
73989         // normalize to the same range of angles created by drawSeries
73990         if (angle > me.firstAngle) {
73991             angle -= 360;
73992         }
73993         return (angle <= startAngle && angle > endAngle
73994                 && rho >= item.startRho && rho <= item.endRho);
73995     },
73996
73997     // @private hides all elements in the series.
73998     hideAll: function() {
73999         var i, l, shadow, shadows, sh, lsh, sprite;
74000         if (!isNaN(this._index)) {
74001             this.__excludes = this.__excludes || [];
74002             this.__excludes[this._index] = true;
74003             sprite = this.slices[this._index].sprite;
74004             for (sh = 0, lsh = sprite.length; sh < lsh; sh++) {
74005                 sprite[sh].setAttributes({
74006                     hidden: true
74007                 }, true);
74008             }
74009             if (this.slices[this._index].shadowAttrs) {
74010                 for (i = 0, shadows = this.slices[this._index].shadowAttrs, l = shadows.length; i < l; i++) {
74011                     shadow = shadows[i];
74012                     for (sh = 0, lsh = shadow.length; sh < lsh; sh++) {
74013                         shadow[sh].setAttributes({
74014                             hidden: true
74015                         }, true);
74016                     }
74017                 }
74018             }
74019             this.drawSeries();
74020         }
74021     },
74022
74023     // @private shows all elements in the series.
74024     showAll: function() {
74025         if (!isNaN(this._index)) {
74026             this.__excludes[this._index] = false;
74027             this.drawSeries();
74028         }
74029     },
74030
74031     /**
74032      * Highlight the specified item. If no item is provided the whole series will be highlighted.
74033      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74034      */
74035     highlightItem: function(item) {
74036         var me = this,
74037             rad = me.rad;
74038         item = item || this.items[this._index];
74039
74040         //TODO(nico): sometimes in IE itemmouseover is triggered
74041         //twice without triggering itemmouseout in between. This
74042         //fixes the highlighting bug. Eventually, events should be
74043         //changed to trigger one itemmouseout between two itemmouseovers.
74044         this.unHighlightItem();
74045
74046         if (!item || item.sprite && item.sprite._animating) {
74047             return;
74048         }
74049         me.callParent([item]);
74050         if (!me.highlight) {
74051             return;
74052         }
74053         if ('segment' in me.highlightCfg) {
74054             var highlightSegment = me.highlightCfg.segment,
74055                 animate = me.chart.animate,
74056                 attrs, i, shadows, shadow, ln, to, itemHighlightSegment, prop;
74057             //animate labels
74058             if (me.labelsGroup) {
74059                 var group = me.labelsGroup,
74060                     display = me.label.display,
74061                     label = group.getAt(item.index),
74062                     middle = (item.startAngle + item.endAngle) / 2 * rad,
74063                     r = highlightSegment.margin || 0,
74064                     x = r * Math.cos(middle),
74065                     y = r * Math.sin(middle);
74066
74067                 //TODO(nico): rounding to 1e-10
74068                 //gives the right translation. Translation
74069                 //was buggy for very small numbers. In this
74070                 //case we're not looking to translate to very small
74071                 //numbers but not to translate at all.
74072                 if (Math.abs(x) < 1e-10) {
74073                     x = 0;
74074                 }
74075                 if (Math.abs(y) < 1e-10) {
74076                     y = 0;
74077                 }
74078
74079                 if (animate) {
74080                     label.stopAnimation();
74081                     label.animate({
74082                         to: {
74083                             translate: {
74084                                 x: x,
74085                                 y: y
74086                             }
74087                         },
74088                         duration: me.highlightDuration
74089                     });
74090                 }
74091                 else {
74092                     label.setAttributes({
74093                         translate: {
74094                             x: x,
74095                             y: y
74096                         }
74097                     }, true);
74098                 }
74099             }
74100             //animate shadows
74101             if (me.chart.shadow && item.shadows) {
74102                 i = 0;
74103                 shadows = item.shadows;
74104                 ln = shadows.length;
74105                 for (; i < ln; i++) {
74106                     shadow = shadows[i];
74107                     to = {};
74108                     itemHighlightSegment = item.sprite._from.segment;
74109                     for (prop in itemHighlightSegment) {
74110                         if (! (prop in highlightSegment)) {
74111                             to[prop] = itemHighlightSegment[prop];
74112                         }
74113                     }
74114                     attrs = {
74115                         segment: Ext.applyIf(to, me.highlightCfg.segment)
74116                     };
74117                     if (animate) {
74118                         shadow.stopAnimation();
74119                         shadow.animate({
74120                             to: attrs,
74121                             duration: me.highlightDuration
74122                         });
74123                     }
74124                     else {
74125                         shadow.setAttributes(attrs, true);
74126                     }
74127                 }
74128             }
74129         }
74130     },
74131
74132     /**
74133      * Un-highlights the specified item. If no item is provided it will un-highlight the entire series.
74134      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74135      */
74136     unHighlightItem: function() {
74137         var me = this;
74138         if (!me.highlight) {
74139             return;
74140         }
74141
74142         if (('segment' in me.highlightCfg) && me.items) {
74143             var items = me.items,
74144                 animate = me.chart.animate,
74145                 shadowsEnabled = !!me.chart.shadow,
74146                 group = me.labelsGroup,
74147                 len = items.length,
74148                 i = 0,
74149                 j = 0,
74150                 display = me.label.display,
74151                 shadowLen, p, to, ihs, hs, sprite, shadows, shadow, item, label, attrs;
74152
74153             for (; i < len; i++) {
74154                 item = items[i];
74155                 if (!item) {
74156                     continue;
74157                 }
74158                 sprite = item.sprite;
74159                 if (sprite && sprite._highlighted) {
74160                     //animate labels
74161                     if (group) {
74162                         label = group.getAt(item.index);
74163                         attrs = Ext.apply({
74164                             translate: {
74165                                 x: 0,
74166                                 y: 0
74167                             }
74168                         },
74169                         display == 'rotate' ? {
74170                             rotate: {
74171                                 x: label.attr.x,
74172                                 y: label.attr.y,
74173                                 degrees: label.attr.rotation.degrees
74174                             }
74175                         }: {});
74176                         if (animate) {
74177                             label.stopAnimation();
74178                             label.animate({
74179                                 to: attrs,
74180                                 duration: me.highlightDuration
74181                             });
74182                         }
74183                         else {
74184                             label.setAttributes(attrs, true);
74185                         }
74186                     }
74187                     if (shadowsEnabled) {
74188                         shadows = item.shadows;
74189                         shadowLen = shadows.length;
74190                         for (; j < shadowLen; j++) {
74191                             to = {};
74192                             ihs = item.sprite._to.segment;
74193                             hs = item.sprite._from.segment;
74194                             Ext.apply(to, hs);
74195                             for (p in ihs) {
74196                                 if (! (p in hs)) {
74197                                     to[p] = ihs[p];
74198                                 }
74199                             }
74200                             shadow = shadows[j];
74201                             if (animate) {
74202                                 shadow.stopAnimation();
74203                                 shadow.animate({
74204                                     to: {
74205                                         segment: to
74206                                     },
74207                                     duration: me.highlightDuration
74208                                 });
74209                             }
74210                             else {
74211                                 shadow.setAttributes({ segment: to }, true);
74212                             }
74213                         }
74214                     }
74215                 }
74216             }
74217         }
74218         me.callParent(arguments);
74219     },
74220
74221     /**
74222      * Returns the color of the series (to be displayed as color for the series legend item).
74223      * @param item {Object} Info about the item; same format as returned by #getItemForPoint
74224      */
74225     getLegendColor: function(index) {
74226         var me = this;
74227         return (me.colorSet && me.colorSet[index % me.colorSet.length]) || me.colorArrayStyle[index % me.colorArrayStyle.length];
74228     }
74229 });
74230
74231
74232 /**
74233  * @class Ext.chart.series.Radar
74234  * @extends Ext.chart.series.Series
74235  *
74236  * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for
74237  * a constrained number of categories.
74238  *
74239  * As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart
74240  * documentation for more information. A typical configuration object for the radar series could be:
74241  *
74242  *     @example
74243  *     var store = Ext.create('Ext.data.JsonStore', {
74244  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
74245  *         data: [
74246  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
74247  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
74248  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
74249  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
74250  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
74251  *         ]
74252  *     });
74253  *
74254  *     Ext.create('Ext.chart.Chart', {
74255  *         renderTo: Ext.getBody(),
74256  *         width: 500,
74257  *         height: 300,
74258  *         animate: true,
74259  *         theme:'Category2',
74260  *         store: store,
74261  *         axes: [{
74262  *             type: 'Radial',
74263  *             position: 'radial',
74264  *             label: {
74265  *                 display: true
74266  *             }
74267  *         }],
74268  *         series: [{
74269  *             type: 'radar',
74270  *             xField: 'name',
74271  *             yField: 'data3',
74272  *             showInLegend: true,
74273  *             showMarkers: true,
74274  *             markerConfig: {
74275  *                 radius: 5,
74276  *                 size: 5
74277  *             },
74278  *             style: {
74279  *                 'stroke-width': 2,
74280  *                 fill: 'none'
74281  *             }
74282  *         },{
74283  *             type: 'radar',
74284  *             xField: 'name',
74285  *             yField: 'data2',
74286  *             showMarkers: true,
74287  *             showInLegend: true,
74288  *             markerConfig: {
74289  *                 radius: 5,
74290  *                 size: 5
74291  *             },
74292  *             style: {
74293  *                 'stroke-width': 2,
74294  *                 fill: 'none'
74295  *             }
74296  *         },{
74297  *             type: 'radar',
74298  *             xField: 'name',
74299  *             yField: 'data5',
74300  *             showMarkers: true,
74301  *             showInLegend: true,
74302  *             markerConfig: {
74303  *                 radius: 5,
74304  *                 size: 5
74305  *             },
74306  *             style: {
74307  *                 'stroke-width': 2,
74308  *                 fill: 'none'
74309  *             }
74310  *         }]
74311  *     });
74312  *
74313  * In this configuration we add three series to the chart. Each of these series is bound to the same
74314  * categories field, `name` but bound to different properties for each category, `data1`, `data2` and
74315  * `data3` respectively. All series display markers by having `showMarkers` enabled. The configuration
74316  * for the markers of each series can be set by adding properties onto the markerConfig object.
74317  * Finally we override some theme styling properties by adding properties to the `style` object.
74318  *
74319  * @xtype radar
74320  */
74321 Ext.define('Ext.chart.series.Radar', {
74322
74323     /* Begin Definitions */
74324
74325     extend: 'Ext.chart.series.Series',
74326
74327     requires: ['Ext.chart.Shape', 'Ext.fx.Anim'],
74328
74329     /* End Definitions */
74330
74331     type: "radar",
74332     alias: 'series.radar',
74333
74334
74335     rad: Math.PI / 180,
74336
74337     showInLegend: false,
74338
74339     /**
74340      * @cfg {Object} style
74341      * An object containing styles for overriding series styles from Theming.
74342      */
74343     style: {},
74344
74345     constructor: function(config) {
74346         this.callParent(arguments);
74347         var me = this,
74348             surface = me.chart.surface, i, l;
74349         me.group = surface.getGroup(me.seriesId);
74350         if (me.showMarkers) {
74351             me.markerGroup = surface.getGroup(me.seriesId + '-markers');
74352         }
74353     },
74354
74355     /**
74356      * Draws the series for the current chart.
74357      */
74358     drawSeries: function() {
74359         var me = this,
74360             store = me.chart.getChartStore(),
74361             group = me.group,
74362             sprite,
74363             chart = me.chart,
74364             animate = chart.animate,
74365             field = me.field || me.yField,
74366             surface = chart.surface,
74367             chartBBox = chart.chartBBox,
74368             rendererAttributes,
74369             centerX, centerY,
74370             items,
74371             radius,
74372             maxValue = 0,
74373             fields = [],
74374             max = Math.max,
74375             cos = Math.cos,
74376             sin = Math.sin,
74377             pi2 = Math.PI * 2,
74378             l = store.getCount(),
74379             startPath, path, x, y, rho,
74380             i, nfields,
74381             seriesStyle = me.seriesStyle,
74382             seriesLabelStyle = me.seriesLabelStyle,
74383             first = chart.resizing || !me.radar,
74384             axis = chart.axes && chart.axes.get(0),
74385             aggregate = !(axis && axis.maximum);
74386
74387         me.setBBox();
74388
74389         maxValue = aggregate? 0 : (axis.maximum || 0);
74390
74391         Ext.apply(seriesStyle, me.style || {});
74392
74393         //if the store is empty then there's nothing to draw
74394         if (!store || !store.getCount()) {
74395             return;
74396         }
74397
74398         me.unHighlightItem();
74399         me.cleanHighlights();
74400
74401         centerX = me.centerX = chartBBox.x + (chartBBox.width / 2);
74402         centerY = me.centerY = chartBBox.y + (chartBBox.height / 2);
74403         me.radius = radius = Math.min(chartBBox.width, chartBBox.height) /2;
74404         me.items = items = [];
74405
74406         if (aggregate) {
74407             //get all renderer fields
74408             chart.series.each(function(series) {
74409                 fields.push(series.yField);
74410             });
74411             //get maxValue to interpolate
74412             store.each(function(record, i) {
74413                 for (i = 0, nfields = fields.length; i < nfields; i++) {
74414                     maxValue = max(+record.get(fields[i]), maxValue);
74415                 }
74416             });
74417         }
74418         //ensure non-zero value.
74419         maxValue = maxValue || 1;
74420         //create path and items
74421         startPath = []; path = [];
74422         store.each(function(record, i) {
74423             rho = radius * record.get(field) / maxValue;
74424             x = rho * cos(i / l * pi2);
74425             y = rho * sin(i / l * pi2);
74426             if (i == 0) {
74427                 path.push('M', x + centerX, y + centerY);
74428                 startPath.push('M', 0.01 * x + centerX, 0.01 * y + centerY);
74429             } else {
74430                 path.push('L', x + centerX, y + centerY);
74431                 startPath.push('L', 0.01 * x + centerX, 0.01 * y + centerY);
74432             }
74433             items.push({
74434                 sprite: false, //TODO(nico): add markers
74435                 point: [centerX + x, centerY + y],
74436                 series: me
74437             });
74438         });
74439         path.push('Z');
74440         //create path sprite
74441         if (!me.radar) {
74442             me.radar = surface.add(Ext.apply({
74443                 type: 'path',
74444                 group: group,
74445                 path: startPath
74446             }, seriesStyle || {}));
74447         }
74448         //reset on resizing
74449         if (chart.resizing) {
74450             me.radar.setAttributes({
74451                 path: startPath
74452             }, true);
74453         }
74454         //render/animate
74455         if (chart.animate) {
74456             me.onAnimate(me.radar, {
74457                 to: Ext.apply({
74458                     path: path
74459                 }, seriesStyle || {})
74460             });
74461         } else {
74462             me.radar.setAttributes(Ext.apply({
74463                 path: path
74464             }, seriesStyle || {}), true);
74465         }
74466         //render markers, labels and callouts
74467         if (me.showMarkers) {
74468             me.drawMarkers();
74469         }
74470         me.renderLabels();
74471         me.renderCallouts();
74472     },
74473
74474     // @private draws the markers for the lines (if any).
74475     drawMarkers: function() {
74476         var me = this,
74477             chart = me.chart,
74478             surface = chart.surface,
74479             markerStyle = Ext.apply({}, me.markerStyle || {}),
74480             endMarkerStyle = Ext.apply(markerStyle, me.markerConfig),
74481             items = me.items,
74482             type = endMarkerStyle.type,
74483             markerGroup = me.markerGroup,
74484             centerX = me.centerX,
74485             centerY = me.centerY,
74486             item, i, l, marker;
74487
74488         delete endMarkerStyle.type;
74489
74490         for (i = 0, l = items.length; i < l; i++) {
74491             item = items[i];
74492             marker = markerGroup.getAt(i);
74493             if (!marker) {
74494                 marker = Ext.chart.Shape[type](surface, Ext.apply({
74495                     group: markerGroup,
74496                     x: 0,
74497                     y: 0,
74498                     translate: {
74499                         x: centerX,
74500                         y: centerY
74501                     }
74502                 }, endMarkerStyle));
74503             }
74504             else {
74505                 marker.show();
74506             }
74507             if (chart.resizing) {
74508                 marker.setAttributes({
74509                     x: 0,
74510                     y: 0,
74511                     translate: {
74512                         x: centerX,
74513                         y: centerY
74514                     }
74515                 }, true);
74516             }
74517             marker._to = {
74518                 translate: {
74519                     x: item.point[0],
74520                     y: item.point[1]
74521                 }
74522             };
74523             //render/animate
74524             if (chart.animate) {
74525                 me.onAnimate(marker, {
74526                     to: marker._to
74527                 });
74528             }
74529             else {
74530                 marker.setAttributes(Ext.apply(marker._to, endMarkerStyle || {}), true);
74531             }
74532         }
74533     },
74534
74535     isItemInPoint: function(x, y, item) {
74536         var point,
74537             tolerance = 10,
74538             abs = Math.abs;
74539         point = item.point;
74540         return (abs(point[0] - x) <= tolerance &&
74541                 abs(point[1] - y) <= tolerance);
74542     },
74543
74544     // @private callback for when creating a label sprite.
74545     onCreateLabel: function(storeItem, item, i, display) {
74546         var me = this,
74547             group = me.labelsGroup,
74548             config = me.label,
74549             centerX = me.centerX,
74550             centerY = me.centerY,
74551             point = item.point,
74552             endLabelStyle = Ext.apply(me.seriesLabelStyle || {}, config);
74553
74554         return me.chart.surface.add(Ext.apply({
74555             'type': 'text',
74556             'text-anchor': 'middle',
74557             'group': group,
74558             'x': centerX,
74559             'y': centerY
74560         }, config || {}));
74561     },
74562
74563     // @private callback for when placing a label sprite.
74564     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
74565         var me = this,
74566             chart = me.chart,
74567             resizing = chart.resizing,
74568             config = me.label,
74569             format = config.renderer,
74570             field = config.field,
74571             centerX = me.centerX,
74572             centerY = me.centerY,
74573             opt = {
74574                 x: item.point[0],
74575                 y: item.point[1]
74576             },
74577             x = opt.x - centerX,
74578             y = opt.y - centerY;
74579
74580         label.setAttributes({
74581             text: format(storeItem.get(field)),
74582             hidden: true
74583         },
74584         true);
74585
74586         if (resizing) {
74587             label.setAttributes({
74588                 x: centerX,
74589                 y: centerY
74590             }, true);
74591         }
74592
74593         if (animate) {
74594             label.show(true);
74595             me.onAnimate(label, {
74596                 to: opt
74597             });
74598         } else {
74599             label.setAttributes(opt, true);
74600             label.show(true);
74601         }
74602     },
74603
74604     // @private for toggling (show/hide) series.
74605     toggleAll: function(show) {
74606         var me = this,
74607             i, ln, shadow, shadows;
74608         if (!show) {
74609             Ext.chart.series.Radar.superclass.hideAll.call(me);
74610         }
74611         else {
74612             Ext.chart.series.Radar.superclass.showAll.call(me);
74613         }
74614         if (me.radar) {
74615             me.radar.setAttributes({
74616                 hidden: !show
74617             }, true);
74618             //hide shadows too
74619             if (me.radar.shadows) {
74620                 for (i = 0, shadows = me.radar.shadows, ln = shadows.length; i < ln; i++) {
74621                     shadow = shadows[i];
74622                     shadow.setAttributes({
74623                         hidden: !show
74624                     }, true);
74625                 }
74626             }
74627         }
74628     },
74629
74630     // @private hide all elements in the series.
74631     hideAll: function() {
74632         this.toggleAll(false);
74633         this.hideMarkers(0);
74634     },
74635
74636     // @private show all elements in the series.
74637     showAll: function() {
74638         this.toggleAll(true);
74639     },
74640
74641     // @private hide all markers that belong to `markerGroup`
74642     hideMarkers: function(index) {
74643         var me = this,
74644             count = me.markerGroup && me.markerGroup.getCount() || 0,
74645             i = index || 0;
74646         for (; i < count; i++) {
74647             me.markerGroup.getAt(i).hide(true);
74648         }
74649     }
74650 });
74651
74652
74653 /**
74654  * @class Ext.chart.series.Scatter
74655  * @extends Ext.chart.series.Cartesian
74656  *
74657  * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization.
74658  * These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
74659  * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart
74660  * documentation for more information on creating charts. A typical configuration object for the scatter could be:
74661  *
74662  *     @example
74663  *     var store = Ext.create('Ext.data.JsonStore', {
74664  *         fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
74665  *         data: [
74666  *             { 'name': 'metric one',   'data1': 10, 'data2': 12, 'data3': 14, 'data4': 8,  'data5': 13 },
74667  *             { 'name': 'metric two',   'data1': 7,  'data2': 8,  'data3': 16, 'data4': 10, 'data5': 3  },
74668  *             { 'name': 'metric three', 'data1': 5,  'data2': 2,  'data3': 14, 'data4': 12, 'data5': 7  },
74669  *             { 'name': 'metric four',  'data1': 2,  'data2': 14, 'data3': 6,  'data4': 1,  'data5': 23 },
74670  *             { 'name': 'metric five',  'data1': 27, 'data2': 38, 'data3': 36, 'data4': 13, 'data5': 33 }
74671  *         ]
74672  *     });
74673  *
74674  *     Ext.create('Ext.chart.Chart', {
74675  *         renderTo: Ext.getBody(),
74676  *         width: 500,
74677  *         height: 300,
74678  *         animate: true,
74679  *         theme:'Category2',
74680  *         store: store,
74681  *         axes: [{
74682  *             type: 'Numeric',
74683  *             position: 'left',
74684  *             fields: ['data2', 'data3'],
74685  *             title: 'Sample Values',
74686  *             grid: true,
74687  *             minimum: 0
74688  *         }, {
74689  *             type: 'Category',
74690  *             position: 'bottom',
74691  *             fields: ['name'],
74692  *             title: 'Sample Metrics'
74693  *         }],
74694  *         series: [{
74695  *             type: 'scatter',
74696  *             markerConfig: {
74697  *                 radius: 5,
74698  *                 size: 5
74699  *             },
74700  *             axis: 'left',
74701  *             xField: 'name',
74702  *             yField: 'data2'
74703  *         }, {
74704  *             type: 'scatter',
74705  *             markerConfig: {
74706  *                 radius: 5,
74707  *                 size: 5
74708  *             },
74709  *             axis: 'left',
74710  *             xField: 'name',
74711  *             yField: 'data3'
74712  *         }]
74713  *     });
74714  *
74715  * 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,
74716  * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`.
74717  * Each scatter series has a different styling configuration for markers, specified by the `markerConfig` object. Finally we set the left axis as
74718  * axis to show the current values of the elements.
74719  *
74720  * @xtype scatter
74721  */
74722 Ext.define('Ext.chart.series.Scatter', {
74723
74724     /* Begin Definitions */
74725
74726     extend: 'Ext.chart.series.Cartesian',
74727
74728     requires: ['Ext.chart.axis.Axis', 'Ext.chart.Shape', 'Ext.fx.Anim'],
74729
74730     /* End Definitions */
74731
74732     type: 'scatter',
74733     alias: 'series.scatter',
74734
74735     /**
74736      * @cfg {Object} markerConfig
74737      * The display style for the scatter series markers.
74738      */
74739
74740     /**
74741      * @cfg {Object} style
74742      * Append styling properties to this object for it to override theme properties.
74743      */
74744     
74745     /**
74746      * @cfg {String/Array} axis
74747      * The position of the axis to bind the values to. Possible values are 'left', 'bottom', 'top' and 'right'.
74748      * You must explicitly set this value to bind the values of the line series to the ones in the axis, otherwise a
74749      * relative scale will be used. If multiple axes are being used, they should both be specified in in the configuration.
74750      */
74751
74752     constructor: function(config) {
74753         this.callParent(arguments);
74754         var me = this,
74755             shadow = me.chart.shadow,
74756             surface = me.chart.surface, i, l;
74757         Ext.apply(me, config, {
74758             style: {},
74759             markerConfig: {},
74760             shadowAttributes: [{
74761                 "stroke-width": 6,
74762                 "stroke-opacity": 0.05,
74763                 stroke: 'rgb(0, 0, 0)'
74764             }, {
74765                 "stroke-width": 4,
74766                 "stroke-opacity": 0.1,
74767                 stroke: 'rgb(0, 0, 0)'
74768             }, {
74769                 "stroke-width": 2,
74770                 "stroke-opacity": 0.15,
74771                 stroke: 'rgb(0, 0, 0)'
74772             }]
74773         });
74774         me.group = surface.getGroup(me.seriesId);
74775         if (shadow) {
74776             for (i = 0, l = me.shadowAttributes.length; i < l; i++) {
74777                 me.shadowGroups.push(surface.getGroup(me.seriesId + '-shadows' + i));
74778             }
74779         }
74780     },
74781
74782     // @private Get chart and data boundaries
74783     getBounds: function() {
74784         var me = this,
74785             chart = me.chart,
74786             store = chart.getChartStore(),
74787             axes = [].concat(me.axis),
74788             bbox, xScale, yScale, ln, minX, minY, maxX, maxY, i, axis, ends;
74789
74790         me.setBBox();
74791         bbox = me.bbox;
74792
74793         for (i = 0, ln = axes.length; i < ln; i++) {
74794             axis = chart.axes.get(axes[i]);
74795             if (axis) {
74796                 ends = axis.calcEnds();
74797                 if (axis.position == 'top' || axis.position == 'bottom') {
74798                     minX = ends.from;
74799                     maxX = ends.to;
74800                 }
74801                 else {
74802                     minY = ends.from;
74803                     maxY = ends.to;
74804                 }
74805             }
74806         }
74807         // If a field was specified without a corresponding axis, create one to get bounds
74808         if (me.xField && !Ext.isNumber(minX)) {
74809             axis = Ext.create('Ext.chart.axis.Axis', {
74810                 chart: chart,
74811                 fields: [].concat(me.xField)
74812             }).calcEnds();
74813             minX = axis.from;
74814             maxX = axis.to;
74815         }
74816         if (me.yField && !Ext.isNumber(minY)) {
74817             axis = Ext.create('Ext.chart.axis.Axis', {
74818                 chart: chart,
74819                 fields: [].concat(me.yField)
74820             }).calcEnds();
74821             minY = axis.from;
74822             maxY = axis.to;
74823         }
74824
74825         if (isNaN(minX)) {
74826             minX = 0;
74827             maxX = store.getCount() - 1;
74828             xScale = bbox.width / (store.getCount() - 1);
74829         }
74830         else {
74831             xScale = bbox.width / (maxX - minX);
74832         }
74833
74834         if (isNaN(minY)) {
74835             minY = 0;
74836             maxY = store.getCount() - 1;
74837             yScale = bbox.height / (store.getCount() - 1);
74838         }
74839         else {
74840             yScale = bbox.height / (maxY - minY);
74841         }
74842
74843         return {
74844             bbox: bbox,
74845             minX: minX,
74846             minY: minY,
74847             xScale: xScale,
74848             yScale: yScale
74849         };
74850     },
74851
74852     // @private Build an array of paths for the chart
74853     getPaths: function() {
74854         var me = this,
74855             chart = me.chart,
74856             enableShadows = chart.shadow,
74857             store = chart.getChartStore(),
74858             group = me.group,
74859             bounds = me.bounds = me.getBounds(),
74860             bbox = me.bbox,
74861             xScale = bounds.xScale,
74862             yScale = bounds.yScale,
74863             minX = bounds.minX,
74864             minY = bounds.minY,
74865             boxX = bbox.x,
74866             boxY = bbox.y,
74867             boxHeight = bbox.height,
74868             items = me.items = [],
74869             attrs = [],
74870             x, y, xValue, yValue, sprite;
74871
74872         store.each(function(record, i) {
74873             xValue = record.get(me.xField);
74874             yValue = record.get(me.yField);
74875             //skip undefined values
74876             if (typeof yValue == 'undefined' || (typeof yValue == 'string' && !yValue)) {
74877                 return;
74878             }
74879             // Ensure a value
74880             if (typeof xValue == 'string' || typeof xValue == 'object' && !Ext.isDate(xValue)) {
74881                 xValue = i;
74882             }
74883             if (typeof yValue == 'string' || typeof yValue == 'object' && !Ext.isDate(yValue)) {
74884                 yValue = i;
74885             }
74886             x = boxX + (xValue - minX) * xScale;
74887             y = boxY + boxHeight - (yValue - minY) * yScale;
74888             attrs.push({
74889                 x: x,
74890                 y: y
74891             });
74892
74893             me.items.push({
74894                 series: me,
74895                 value: [xValue, yValue],
74896                 point: [x, y],
74897                 storeItem: record
74898             });
74899
74900             // When resizing, reset before animating
74901             if (chart.animate && chart.resizing) {
74902                 sprite = group.getAt(i);
74903                 if (sprite) {
74904                     me.resetPoint(sprite);
74905                     if (enableShadows) {
74906                         me.resetShadow(sprite);
74907                     }
74908                 }
74909             }
74910         });
74911         return attrs;
74912     },
74913
74914     // @private translate point to the center
74915     resetPoint: function(sprite) {
74916         var bbox = this.bbox;
74917         sprite.setAttributes({
74918             translate: {
74919                 x: (bbox.x + bbox.width) / 2,
74920                 y: (bbox.y + bbox.height) / 2
74921             }
74922         }, true);
74923     },
74924
74925     // @private translate shadows of a sprite to the center
74926     resetShadow: function(sprite) {
74927         var me = this,
74928             shadows = sprite.shadows,
74929             shadowAttributes = me.shadowAttributes,
74930             ln = me.shadowGroups.length,
74931             bbox = me.bbox,
74932             i, attr;
74933         for (i = 0; i < ln; i++) {
74934             attr = Ext.apply({}, shadowAttributes[i]);
74935             if (attr.translate) {
74936                 attr.translate.x += (bbox.x + bbox.width) / 2;
74937                 attr.translate.y += (bbox.y + bbox.height) / 2;
74938             }
74939             else {
74940                 attr.translate = {
74941                     x: (bbox.x + bbox.width) / 2,
74942                     y: (bbox.y + bbox.height) / 2
74943                 };
74944             }
74945             shadows[i].setAttributes(attr, true);
74946         }
74947     },
74948
74949     // @private create a new point
74950     createPoint: function(attr, type) {
74951         var me = this,
74952             chart = me.chart,
74953             group = me.group,
74954             bbox = me.bbox;
74955
74956         return Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
74957             x: 0,
74958             y: 0,
74959             group: group,
74960             translate: {
74961                 x: (bbox.x + bbox.width) / 2,
74962                 y: (bbox.y + bbox.height) / 2
74963             }
74964         }, attr));
74965     },
74966
74967     // @private create a new set of shadows for a sprite
74968     createShadow: function(sprite, endMarkerStyle, type) {
74969         var me = this,
74970             chart = me.chart,
74971             shadowGroups = me.shadowGroups,
74972             shadowAttributes = me.shadowAttributes,
74973             lnsh = shadowGroups.length,
74974             bbox = me.bbox,
74975             i, shadow, shadows, attr;
74976
74977         sprite.shadows = shadows = [];
74978
74979         for (i = 0; i < lnsh; i++) {
74980             attr = Ext.apply({}, shadowAttributes[i]);
74981             if (attr.translate) {
74982                 attr.translate.x += (bbox.x + bbox.width) / 2;
74983                 attr.translate.y += (bbox.y + bbox.height) / 2;
74984             }
74985             else {
74986                 Ext.apply(attr, {
74987                     translate: {
74988                         x: (bbox.x + bbox.width) / 2,
74989                         y: (bbox.y + bbox.height) / 2
74990                     }
74991                 });
74992             }
74993             Ext.apply(attr, endMarkerStyle);
74994             shadow = Ext.chart.Shape[type](chart.surface, Ext.apply({}, {
74995                 x: 0,
74996                 y: 0,
74997                 group: shadowGroups[i]
74998             }, attr));
74999             shadows.push(shadow);
75000         }
75001     },
75002
75003     /**
75004      * Draws the series for the current chart.
75005      */
75006     drawSeries: function() {
75007         var me = this,
75008             chart = me.chart,
75009             store = chart.getChartStore(),
75010             group = me.group,
75011             enableShadows = chart.shadow,
75012             shadowGroups = me.shadowGroups,
75013             shadowAttributes = me.shadowAttributes,
75014             lnsh = shadowGroups.length,
75015             sprite, attrs, attr, ln, i, endMarkerStyle, shindex, type, shadows,
75016             rendererAttributes, shadowAttribute;
75017
75018         endMarkerStyle = Ext.apply(me.markerStyle, me.markerConfig);
75019         type = endMarkerStyle.type;
75020         delete endMarkerStyle.type;
75021
75022         //if the store is empty then there's nothing to be rendered
75023         if (!store || !store.getCount()) {
75024             return;
75025         }
75026
75027         me.unHighlightItem();
75028         me.cleanHighlights();
75029
75030         attrs = me.getPaths();
75031         ln = attrs.length;
75032         for (i = 0; i < ln; i++) {
75033             attr = attrs[i];
75034             sprite = group.getAt(i);
75035             Ext.apply(attr, endMarkerStyle);
75036
75037             // Create a new sprite if needed (no height)
75038             if (!sprite) {
75039                 sprite = me.createPoint(attr, type);
75040                 if (enableShadows) {
75041                     me.createShadow(sprite, endMarkerStyle, type);
75042                 }
75043             }
75044
75045             shadows = sprite.shadows;
75046             if (chart.animate) {
75047                 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
75048                 sprite._to = rendererAttributes;
75049                 me.onAnimate(sprite, {
75050                     to: rendererAttributes
75051                 });
75052                 //animate shadows
75053                 for (shindex = 0; shindex < lnsh; shindex++) {
75054                     shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
75055                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
75056                         hidden: false,
75057                         translate: {
75058                             x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
75059                             y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
75060                         }
75061                     }, shadowAttribute), i, store);
75062                     me.onAnimate(shadows[shindex], { to: rendererAttributes });
75063                 }
75064             }
75065             else {
75066                 rendererAttributes = me.renderer(sprite, store.getAt(i), { translate: attr }, i, store);
75067                 sprite._to = rendererAttributes;
75068                 sprite.setAttributes(rendererAttributes, true);
75069                 //animate shadows
75070                 for (shindex = 0; shindex < lnsh; shindex++) {
75071                     shadowAttribute = Ext.apply({}, shadowAttributes[shindex]);
75072                     rendererAttributes = me.renderer(shadows[shindex], store.getAt(i), Ext.apply({}, { 
75073                         hidden: false,
75074                         translate: {
75075                             x: attr.x + (shadowAttribute.translate? shadowAttribute.translate.x : 0),
75076                             y: attr.y + (shadowAttribute.translate? shadowAttribute.translate.y : 0)
75077                         } 
75078                     }, shadowAttribute), i, store);
75079                     shadows[shindex].setAttributes(rendererAttributes, true);
75080                 }
75081             }
75082             me.items[i].sprite = sprite;
75083         }
75084
75085         // Hide unused sprites
75086         ln = group.getCount();
75087         for (i = attrs.length; i < ln; i++) {
75088             group.getAt(i).hide(true);
75089         }
75090         me.renderLabels();
75091         me.renderCallouts();
75092     },
75093
75094     // @private callback for when creating a label sprite.
75095     onCreateLabel: function(storeItem, item, i, display) {
75096         var me = this,
75097             group = me.labelsGroup,
75098             config = me.label,
75099             endLabelStyle = Ext.apply({}, config, me.seriesLabelStyle),
75100             bbox = me.bbox;
75101
75102         return me.chart.surface.add(Ext.apply({
75103             type: 'text',
75104             group: group,
75105             x: item.point[0],
75106             y: bbox.y + bbox.height / 2
75107         }, endLabelStyle));
75108     },
75109
75110     // @private callback for when placing a label sprite.
75111     onPlaceLabel: function(label, storeItem, item, i, display, animate) {
75112         var me = this,
75113             chart = me.chart,
75114             resizing = chart.resizing,
75115             config = me.label,
75116             format = config.renderer,
75117             field = config.field,
75118             bbox = me.bbox,
75119             x = item.point[0],
75120             y = item.point[1],
75121             radius = item.sprite.attr.radius,
75122             bb, width, height, anim;
75123
75124         label.setAttributes({
75125             text: format(storeItem.get(field)),
75126             hidden: true
75127         }, true);
75128
75129         if (display == 'rotate') {
75130             label.setAttributes({
75131                 'text-anchor': 'start',
75132                 'rotation': {
75133                     x: x,
75134                     y: y,
75135                     degrees: -45
75136                 }
75137             }, true);
75138             //correct label position to fit into the box
75139             bb = label.getBBox();
75140             width = bb.width;
75141             height = bb.height;
75142             x = x < bbox.x? bbox.x : x;
75143             x = (x + width > bbox.x + bbox.width)? (x - (x + width - bbox.x - bbox.width)) : x;
75144             y = (y - height < bbox.y)? bbox.y + height : y;
75145
75146         } else if (display == 'under' || display == 'over') {
75147             //TODO(nicolas): find out why width/height values in circle bounding boxes are undefined.
75148             bb = item.sprite.getBBox();
75149             bb.width = bb.width || (radius * 2);
75150             bb.height = bb.height || (radius * 2);
75151             y = y + (display == 'over'? -bb.height : bb.height);
75152             //correct label position to fit into the box
75153             bb = label.getBBox();
75154             width = bb.width/2;
75155             height = bb.height/2;
75156             x = x - width < bbox.x ? bbox.x + width : x;
75157             x = (x + width > bbox.x + bbox.width) ? (x - (x + width - bbox.x - bbox.width)) : x;
75158             y = y - height < bbox.y? bbox.y + height : y;
75159             y = (y + height > bbox.y + bbox.height) ? (y - (y + height - bbox.y - bbox.height)) : y;
75160         }
75161
75162         if (!chart.animate) {
75163             label.setAttributes({
75164                 x: x,
75165                 y: y
75166             }, true);
75167             label.show(true);
75168         }
75169         else {
75170             if (resizing) {
75171                 anim = item.sprite.getActiveAnimation();
75172                 if (anim) {
75173                     anim.on('afteranimate', function() {
75174                         label.setAttributes({
75175                             x: x,
75176                             y: y
75177                         }, true);
75178                         label.show(true);
75179                     });
75180                 }
75181                 else {
75182                     label.show(true);
75183                 }
75184             }
75185             else {
75186                 me.onAnimate(label, {
75187                     to: {
75188                         x: x,
75189                         y: y
75190                     }
75191                 });
75192             }
75193         }
75194     },
75195
75196     // @private callback for when placing a callout sprite.
75197     onPlaceCallout: function(callout, storeItem, item, i, display, animate, index) {
75198         var me = this,
75199             chart = me.chart,
75200             surface = chart.surface,
75201             resizing = chart.resizing,
75202             config = me.callouts,
75203             items = me.items,
75204             cur = item.point,
75205             normal,
75206             bbox = callout.label.getBBox(),
75207             offsetFromViz = 30,
75208             offsetToSide = 10,
75209             offsetBox = 3,
75210             boxx, boxy, boxw, boxh,
75211             p, clipRect = me.bbox,
75212             x, y;
75213
75214         //position
75215         normal = [Math.cos(Math.PI /4), -Math.sin(Math.PI /4)];
75216         x = cur[0] + normal[0] * offsetFromViz;
75217         y = cur[1] + normal[1] * offsetFromViz;
75218
75219         //box position and dimensions
75220         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
75221         boxy = y - bbox.height /2 - offsetBox;
75222         boxw = bbox.width + 2 * offsetBox;
75223         boxh = bbox.height + 2 * offsetBox;
75224
75225         //now check if we're out of bounds and invert the normal vector correspondingly
75226         //this may add new overlaps between labels (but labels won't be out of bounds).
75227         if (boxx < clipRect[0] || (boxx + boxw) > (clipRect[0] + clipRect[2])) {
75228             normal[0] *= -1;
75229         }
75230         if (boxy < clipRect[1] || (boxy + boxh) > (clipRect[1] + clipRect[3])) {
75231             normal[1] *= -1;
75232         }
75233
75234         //update positions
75235         x = cur[0] + normal[0] * offsetFromViz;
75236         y = cur[1] + normal[1] * offsetFromViz;
75237
75238         //update box position and dimensions
75239         boxx = x + (normal[0] > 0? 0 : -(bbox.width + 2 * offsetBox));
75240         boxy = y - bbox.height /2 - offsetBox;
75241         boxw = bbox.width + 2 * offsetBox;
75242         boxh = bbox.height + 2 * offsetBox;
75243
75244         if (chart.animate) {
75245             //set the line from the middle of the pie to the box.
75246             me.onAnimate(callout.lines, {
75247                 to: {
75248                     path: ["M", cur[0], cur[1], "L", x, y, "Z"]
75249                 }
75250             }, true);
75251             //set box position
75252             me.onAnimate(callout.box, {
75253                 to: {
75254                     x: boxx,
75255                     y: boxy,
75256                     width: boxw,
75257                     height: boxh
75258                 }
75259             }, true);
75260             //set text position
75261             me.onAnimate(callout.label, {
75262                 to: {
75263                     x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
75264                     y: y
75265                 }
75266             }, true);
75267         } else {
75268             //set the line from the middle of the pie to the box.
75269             callout.lines.setAttributes({
75270                 path: ["M", cur[0], cur[1], "L", x, y, "Z"]
75271             }, true);
75272             //set box position
75273             callout.box.setAttributes({
75274                 x: boxx,
75275                 y: boxy,
75276                 width: boxw,
75277                 height: boxh
75278             }, true);
75279             //set text position
75280             callout.label.setAttributes({
75281                 x: x + (normal[0] > 0? offsetBox : -(bbox.width + offsetBox)),
75282                 y: y
75283             }, true);
75284         }
75285         for (p in callout) {
75286             callout[p].show(true);
75287         }
75288     },
75289
75290     // @private handles sprite animation for the series.
75291     onAnimate: function(sprite, attr) {
75292         sprite.show();
75293         return this.callParent(arguments);
75294     },
75295
75296     isItemInPoint: function(x, y, item) {
75297         var point,
75298             tolerance = 10,
75299             abs = Math.abs;
75300
75301         function dist(point) {
75302             var dx = abs(point[0] - x),
75303                 dy = abs(point[1] - y);
75304             return Math.sqrt(dx * dx + dy * dy);
75305         }
75306         point = item.point;
75307         return (point[0] - tolerance <= x && point[0] + tolerance >= x &&
75308             point[1] - tolerance <= y && point[1] + tolerance >= y);
75309     }
75310 });
75311
75312
75313 /**
75314  * @class Ext.chart.theme.Base
75315  * Provides default colors for non-specified things. Should be sub-classed when creating new themes.
75316  * @ignore
75317  */
75318 Ext.define('Ext.chart.theme.Base', {
75319
75320     /* Begin Definitions */
75321
75322     requires: ['Ext.chart.theme.Theme'],
75323
75324     /* End Definitions */
75325
75326     constructor: function(config) {
75327         Ext.chart.theme.call(this, config, {
75328             background: false,
75329             axis: {
75330                 stroke: '#444',
75331                 'stroke-width': 1
75332             },
75333             axisLabelTop: {
75334                 fill: '#444',
75335                 font: '12px Arial, Helvetica, sans-serif',
75336                 spacing: 2,
75337                 padding: 5,
75338                 renderer: function(v) { return v; }
75339             },
75340             axisLabelRight: {
75341                 fill: '#444',
75342                 font: '12px Arial, Helvetica, sans-serif',
75343                 spacing: 2,
75344                 padding: 5,
75345                 renderer: function(v) { return v; }
75346             },
75347             axisLabelBottom: {
75348                 fill: '#444',
75349                 font: '12px Arial, Helvetica, sans-serif',
75350                 spacing: 2,
75351                 padding: 5,
75352                 renderer: function(v) { return v; }
75353             },
75354             axisLabelLeft: {
75355                 fill: '#444',
75356                 font: '12px Arial, Helvetica, sans-serif',
75357                 spacing: 2,
75358                 padding: 5,
75359                 renderer: function(v) { return v; }
75360             },
75361             axisTitleTop: {
75362                 font: 'bold 18px Arial',
75363                 fill: '#444'
75364             },
75365             axisTitleRight: {
75366                 font: 'bold 18px Arial',
75367                 fill: '#444',
75368                 rotate: {
75369                     x:0, y:0,
75370                     degrees: 270
75371                 }
75372             },
75373             axisTitleBottom: {
75374                 font: 'bold 18px Arial',
75375                 fill: '#444'
75376             },
75377             axisTitleLeft: {
75378                 font: 'bold 18px Arial',
75379                 fill: '#444',
75380                 rotate: {
75381                     x:0, y:0,
75382                     degrees: 270
75383                 }
75384             },
75385             series: {
75386                 'stroke-width': 0
75387             },
75388             seriesLabel: {
75389                 font: '12px Arial',
75390                 fill: '#333'
75391             },
75392             marker: {
75393                 stroke: '#555',
75394                 fill: '#000',
75395                 radius: 3,
75396                 size: 3
75397             },
75398             colors: [ "#94ae0a", "#115fa6","#a61120", "#ff8809", "#ffd13e", "#a61187", "#24ad9a", "#7c7474", "#a66111"],
75399             seriesThemes: [{
75400                 fill: "#115fa6"
75401             }, {
75402                 fill: "#94ae0a"
75403             }, {
75404                 fill: "#a61120"
75405             }, {
75406                 fill: "#ff8809"
75407             }, {
75408                 fill: "#ffd13e"
75409             }, {
75410                 fill: "#a61187"
75411             }, {
75412                 fill: "#24ad9a"
75413             }, {
75414                 fill: "#7c7474"
75415             }, {
75416                 fill: "#a66111"
75417             }],
75418             markerThemes: [{
75419                 fill: "#115fa6",
75420                 type: 'circle' 
75421             }, {
75422                 fill: "#94ae0a",
75423                 type: 'cross'
75424             }, {
75425                 fill: "#a61120",
75426                 type: 'plus'
75427             }]
75428         });
75429     }
75430 }, function() {
75431     var palette = ['#b1da5a', '#4ce0e7', '#e84b67', '#da5abd', '#4d7fe6', '#fec935'],
75432         names = ['Green', 'Sky', 'Red', 'Purple', 'Blue', 'Yellow'],
75433         i = 0, j = 0, l = palette.length, themes = Ext.chart.theme,
75434         categories = [['#f0a50a', '#c20024', '#2044ba', '#810065', '#7eae29'],
75435                       ['#6d9824', '#87146e', '#2a9196', '#d39006', '#1e40ac'],
75436                       ['#fbbc29', '#ce2e4e', '#7e0062', '#158b90', '#57880e'],
75437                       ['#ef5773', '#fcbd2a', '#4f770d', '#1d3eaa', '#9b001f'],
75438                       ['#7eae29', '#fdbe2a', '#910019', '#27b4bc', '#d74dbc'],
75439                       ['#44dce1', '#0b2592', '#996e05', '#7fb325', '#b821a1']],
75440         cats = categories.length;
75441     
75442     //Create themes from base colors
75443     for (; i < l; i++) {
75444         themes[names[i]] = (function(color) {
75445             return Ext.extend(themes.Base, {
75446                 constructor: function(config) {
75447                     themes.Base.prototype.constructor.call(this, Ext.apply({
75448                         baseColor: color
75449                     }, config));
75450                 }
75451             });
75452         })(palette[i]);
75453     }
75454     
75455     //Create theme from color array
75456     for (i = 0; i < cats; i++) {
75457         themes['Category' + (i + 1)] = (function(category) {
75458             return Ext.extend(themes.Base, {
75459                 constructor: function(config) {
75460                     themes.Base.prototype.constructor.call(this, Ext.apply({
75461                         colors: category
75462                     }, config));
75463                 }
75464             });
75465         })(categories[i]);
75466     }
75467 });
75468
75469 /**
75470  * @author Ed Spencer
75471  *
75472  * Small helper class to make creating {@link Ext.data.Store}s from Array data easier. An ArrayStore will be
75473  * automatically configured with a {@link Ext.data.reader.Array}.
75474  *
75475  * A store configuration would be something like:
75476  *
75477  *     var store = Ext.create('Ext.data.ArrayStore', {
75478  *         // store configs
75479  *         autoDestroy: true,
75480  *         storeId: 'myStore',
75481  *         // reader configs
75482  *         idIndex: 0,
75483  *         fields: [
75484  *            'company',
75485  *            {name: 'price', type: 'float'},
75486  *            {name: 'change', type: 'float'},
75487  *            {name: 'pctChange', type: 'float'},
75488  *            {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
75489  *         ]
75490  *     });
75491  *
75492  * This store is configured to consume a returned object of the form:
75493  *
75494  *     var myData = [
75495  *         ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
75496  *         ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
75497  *         ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
75498  *         ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
75499  *         ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
75500  *     ];
75501  *
75502  * An object literal of this form could also be used as the {@link #data} config option.
75503  *
75504  * **Note:** This class accepts all of the configuration options of {@link Ext.data.reader.Array ArrayReader}.
75505  */
75506 Ext.define('Ext.data.ArrayStore', {
75507     extend: 'Ext.data.Store',
75508     alias: 'store.array',
75509     uses: ['Ext.data.reader.Array'],
75510
75511     constructor: function(config) {
75512         config = config || {};
75513
75514         Ext.applyIf(config, {
75515             proxy: {
75516                 type: 'memory',
75517                 reader: 'array'
75518             }
75519         });
75520
75521         this.callParent([config]);
75522     },
75523
75524     loadData: function(data, append) {
75525         if (this.expandData === true) {
75526             var r = [],
75527                 i = 0,
75528                 ln = data.length;
75529
75530             for (; i < ln; i++) {
75531                 r[r.length] = [data[i]];
75532             }
75533
75534             data = r;
75535         }
75536
75537         this.callParent([data, append]);
75538     }
75539 }, function() {
75540     // backwards compat
75541     Ext.data.SimpleStore = Ext.data.ArrayStore;
75542     // Ext.reg('simplestore', Ext.data.SimpleStore);
75543 });
75544
75545 /**
75546  * @author Ed Spencer
75547  * @class Ext.data.Batch
75548  *
75549  * <p>Provides a mechanism to run one or more {@link Ext.data.Operation operations} in a given order. Fires the 'operationcomplete' event
75550  * after the completion of each Operation, and the 'complete' event when all Operations have been successfully executed. Fires an 'exception'
75551  * event if any of the Operations encounter an exception.</p>
75552  *
75553  * <p>Usually these are only used internally by {@link Ext.data.proxy.Proxy} classes</p>
75554  *
75555  */
75556 Ext.define('Ext.data.Batch', {
75557     mixins: {
75558         observable: 'Ext.util.Observable'
75559     },
75560
75561     /**
75562      * @property {Boolean} autoStart
75563      * True to immediately start processing the batch as soon as it is constructed.
75564      */
75565     autoStart: false,
75566
75567     /**
75568      * @property {Number} current
75569      * The index of the current operation being executed
75570      */
75571     current: -1,
75572
75573     /**
75574      * @property {Number} total
75575      * The total number of operations in this batch. Read only
75576      */
75577     total: 0,
75578
75579     /**
75580      * @property {Boolean} isRunning
75581      * True if the batch is currently running
75582      */
75583     isRunning: false,
75584
75585     /**
75586      * @property {Boolean} isComplete
75587      * True if this batch has been executed completely
75588      */
75589     isComplete: false,
75590
75591     /**
75592      * @property {Boolean} hasException
75593      * True if this batch has encountered an exception. This is cleared at the start of each operation
75594      */
75595     hasException: false,
75596
75597     /**
75598      * @property {Boolean} pauseOnException
75599      * True to automatically pause the execution of the batch if any operation encounters an exception
75600      */
75601     pauseOnException: true,
75602
75603     /**
75604      * Creates new Batch object.
75605      * @param {Object} [config] Config object
75606      */
75607     constructor: function(config) {
75608         var me = this;
75609
75610         me.addEvents(
75611           /**
75612            * @event complete
75613            * Fired when all operations of this batch have been completed
75614            * @param {Ext.data.Batch} batch The batch object
75615            * @param {Object} operation The last operation that was executed
75616            */
75617           'complete',
75618
75619           /**
75620            * @event exception
75621            * Fired when a operation encountered an exception
75622            * @param {Ext.data.Batch} batch The batch object
75623            * @param {Object} operation The operation that encountered the exception
75624            */
75625           'exception',
75626
75627           /**
75628            * @event operationcomplete
75629            * Fired when each operation of the batch completes
75630            * @param {Ext.data.Batch} batch The batch object
75631            * @param {Object} operation The operation that just completed
75632            */
75633           'operationcomplete'
75634         );
75635
75636         me.mixins.observable.constructor.call(me, config);
75637
75638         /**
75639          * Ordered array of operations that will be executed by this batch
75640          * @property {Ext.data.Operation[]} operations
75641          */
75642         me.operations = [];
75643     },
75644
75645     /**
75646      * Adds a new operation to this batch
75647      * @param {Object} operation The {@link Ext.data.Operation Operation} object
75648      */
75649     add: function(operation) {
75650         this.total++;
75651
75652         operation.setBatch(this);
75653
75654         this.operations.push(operation);
75655     },
75656
75657     /**
75658      * Kicks off the execution of the batch, continuing from the next operation if the previous
75659      * operation encountered an exception, or if execution was paused
75660      */
75661     start: function() {
75662         this.hasException = false;
75663         this.isRunning = true;
75664
75665         this.runNextOperation();
75666     },
75667
75668     /**
75669      * @private
75670      * Runs the next operation, relative to this.current.
75671      */
75672     runNextOperation: function() {
75673         this.runOperation(this.current + 1);
75674     },
75675
75676     /**
75677      * Pauses execution of the batch, but does not cancel the current operation
75678      */
75679     pause: function() {
75680         this.isRunning = false;
75681     },
75682
75683     /**
75684      * Executes a operation by its numeric index
75685      * @param {Number} index The operation index to run
75686      */
75687     runOperation: function(index) {
75688         var me = this,
75689             operations = me.operations,
75690             operation  = operations[index],
75691             onProxyReturn;
75692
75693         if (operation === undefined) {
75694             me.isRunning  = false;
75695             me.isComplete = true;
75696             me.fireEvent('complete', me, operations[operations.length - 1]);
75697         } else {
75698             me.current = index;
75699
75700             onProxyReturn = function(operation) {
75701                 var hasException = operation.hasException();
75702
75703                 if (hasException) {
75704                     me.hasException = true;
75705                     me.fireEvent('exception', me, operation);
75706                 } else {
75707                     me.fireEvent('operationcomplete', me, operation);
75708                 }
75709
75710                 if (hasException && me.pauseOnException) {
75711                     me.pause();
75712                 } else {
75713                     operation.setCompleted();
75714                     me.runNextOperation();
75715                 }
75716             };
75717
75718             operation.setStarted();
75719
75720             me.proxy[operation.action](operation, onProxyReturn, me);
75721         }
75722     }
75723 });
75724 /**
75725  * @author Ed Spencer
75726  * @class Ext.data.BelongsToAssociation
75727  * @extends Ext.data.Association
75728  *
75729  * Represents a many to one association with another model. The owner model is expected to have
75730  * a foreign key which references the primary key of the associated model:
75731  *
75732  *     Ext.define('Category', {
75733  *         extend: 'Ext.data.Model',
75734  *         fields: [
75735  *             { name: 'id',   type: 'int' },
75736  *             { name: 'name', type: 'string' }
75737  *         ]
75738  *     });
75739  *
75740  *     Ext.define('Product', {
75741  *         extend: 'Ext.data.Model',
75742  *         fields: [
75743  *             { name: 'id',          type: 'int' },
75744  *             { name: 'category_id', type: 'int' },
75745  *             { name: 'name',        type: 'string' }
75746  *         ],
75747  *         // we can use the belongsTo shortcut on the model to create a belongsTo association
75748  *         associations: [
75749  *             { type: 'belongsTo', model: 'Category' }
75750  *         ]
75751  *     });
75752  *
75753  * In the example above we have created models for Products and Categories, and linked them together
75754  * by saying that each Product belongs to a Category. This automatically links each Product to a Category
75755  * based on the Product's category_id, and provides new functions on the Product model:
75756  *
75757  * ## Generated getter function
75758  *
75759  * The first function that is added to the owner model is a getter function:
75760  *
75761  *     var product = new Product({
75762  *         id: 100,
75763  *         category_id: 20,
75764  *         name: 'Sneakers'
75765  *     });
75766  *
75767  *     product.getCategory(function(category, operation) {
75768  *         // do something with the category object
75769  *         alert(category.get('id')); // alerts 20
75770  *     }, this);
75771  *
75772  * The getCategory function was created on the Product model when we defined the association. This uses the
75773  * Category's configured {@link Ext.data.proxy.Proxy proxy} to load the Category asynchronously, calling the provided
75774  * callback when it has loaded.
75775  *
75776  * The new getCategory function will also accept an object containing success, failure and callback properties
75777  * - callback will always be called, success will only be called if the associated model was loaded successfully
75778  * and failure will only be called if the associatied model could not be loaded:
75779  *
75780  *     product.getCategory({
75781  *         callback: function(category, operation) {}, // a function that will always be called
75782  *         success : function(category, operation) {}, // a function that will only be called if the load succeeded
75783  *         failure : function(category, operation) {}, // a function that will only be called if the load did not succeed
75784  *         scope   : this // optionally pass in a scope object to execute the callbacks in
75785  *     });
75786  *
75787  * In each case above the callbacks are called with two arguments - the associated model instance and the
75788  * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
75789  * useful when the instance could not be loaded.
75790  *
75791  * ## Generated setter function
75792  *
75793  * The second generated function sets the associated model instance - if only a single argument is passed to
75794  * the setter then the following two calls are identical:
75795  *
75796  *     // this call...
75797  *     product.setCategory(10);
75798  *
75799  *     // is equivalent to this call:
75800  *     product.set('category_id', 10);
75801  *
75802  * If we pass in a second argument, the model will be automatically saved and the second argument passed to
75803  * the owner model's {@link Ext.data.Model#save save} method:
75804  *
75805  *     product.setCategory(10, function(product, operation) {
75806  *         // the product has been saved
75807  *         alert(product.get('category_id')); //now alerts 10
75808  *     });
75809  *
75810  *     //alternative syntax:
75811  *     product.setCategory(10, {
75812  *         callback: function(product, operation), // a function that will always be called
75813  *         success : function(product, operation), // a function that will only be called if the load succeeded
75814  *         failure : function(product, operation), // a function that will only be called if the load did not succeed
75815  *         scope   : this //optionally pass in a scope object to execute the callbacks in
75816  *     })
75817  *
75818  * ## Customisation
75819  *
75820  * Associations reflect on the models they are linking to automatically set up properties such as the
75821  * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
75822  *
75823  *     Ext.define('Product', {
75824  *         fields: [...],
75825  *
75826  *         associations: [
75827  *             { type: 'belongsTo', model: 'Category', primaryKey: 'unique_id', foreignKey: 'cat_id' }
75828  *         ]
75829  *     });
75830  *
75831  * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'category_id')
75832  * with our own settings. Usually this will not be needed.
75833  */
75834 Ext.define('Ext.data.BelongsToAssociation', {
75835     extend: 'Ext.data.Association',
75836
75837     alias: 'association.belongsto',
75838
75839     /**
75840      * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
75841      * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
75842      * model called Product would set up a product_id foreign key.
75843      *
75844      *     Ext.define('Order', {
75845      *         extend: 'Ext.data.Model',
75846      *         fields: ['id', 'date'],
75847      *         hasMany: 'Product'
75848      *     });
75849      *
75850      *     Ext.define('Product', {
75851      *         extend: 'Ext.data.Model',
75852      *         fields: ['id', 'name', 'order_id'], // refers to the id of the order that this product belongs to
75853      *         belongsTo: 'Group'
75854      *     });
75855      *     var product = new Product({
75856      *         id: 1,
75857      *         name: 'Product 1',
75858      *         order_id: 22
75859      *     }, 1);
75860      *     product.getOrder(); // Will make a call to the server asking for order_id 22
75861      *
75862      */
75863
75864     /**
75865      * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
75866      * Defaults to 'get' + the name of the foreign model, e.g. getCategory
75867      */
75868
75869     /**
75870      * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
75871      * Defaults to 'set' + the name of the foreign model, e.g. setCategory
75872      */
75873
75874     /**
75875      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
75876      * Use 'belongsTo' to create a HasManyAssocation
75877      *
75878      *     associations: [{
75879      *         type: 'belongsTo',
75880      *         model: 'User'
75881      *     }]
75882      */
75883     constructor: function(config) {
75884         this.callParent(arguments);
75885
75886         var me             = this,
75887             ownerProto     = me.ownerModel.prototype,
75888             associatedName = me.associatedName,
75889             getterName     = me.getterName || 'get' + associatedName,
75890             setterName     = me.setterName || 'set' + associatedName;
75891
75892         Ext.applyIf(me, {
75893             name        : associatedName,
75894             foreignKey  : associatedName.toLowerCase() + "_id",
75895             instanceName: associatedName + 'BelongsToInstance',
75896             associationKey: associatedName.toLowerCase()
75897         });
75898
75899         ownerProto[getterName] = me.createGetter();
75900         ownerProto[setterName] = me.createSetter();
75901     },
75902
75903     /**
75904      * @private
75905      * Returns a setter function to be placed on the owner model's prototype
75906      * @return {Function} The setter function
75907      */
75908     createSetter: function() {
75909         var me              = this,
75910             ownerModel      = me.ownerModel,
75911             associatedModel = me.associatedModel,
75912             foreignKey      = me.foreignKey,
75913             primaryKey      = me.primaryKey;
75914
75915         //'this' refers to the Model instance inside this function
75916         return function(value, options, scope) {
75917             this.set(foreignKey, value);
75918
75919             if (typeof options == 'function') {
75920                 options = {
75921                     callback: options,
75922                     scope: scope || this
75923                 };
75924             }
75925
75926             if (Ext.isObject(options)) {
75927                 return this.save(options);
75928             }
75929         };
75930     },
75931
75932     /**
75933      * @private
75934      * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
75935      * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
75936      * @return {Function} The getter function
75937      */
75938     createGetter: function() {
75939         var me              = this,
75940             ownerModel      = me.ownerModel,
75941             associatedName  = me.associatedName,
75942             associatedModel = me.associatedModel,
75943             foreignKey      = me.foreignKey,
75944             primaryKey      = me.primaryKey,
75945             instanceName    = me.instanceName;
75946
75947         //'this' refers to the Model instance inside this function
75948         return function(options, scope) {
75949             options = options || {};
75950
75951             var model = this,
75952                 foreignKeyId = model.get(foreignKey),
75953                 instance,
75954                 args;
75955
75956             if (model[instanceName] === undefined) {
75957                 instance = Ext.ModelManager.create({}, associatedName);
75958                 instance.set(primaryKey, foreignKeyId);
75959
75960                 if (typeof options == 'function') {
75961                     options = {
75962                         callback: options,
75963                         scope: scope || model
75964                     };
75965                 }
75966
75967                 associatedModel.load(foreignKeyId, options);
75968                 model[instanceName] = associatedModel;
75969                 return associatedModel;
75970             } else {
75971                 instance = model[instanceName];
75972                 args = [instance];
75973                 scope = scope || model;
75974
75975                 //TODO: We're duplicating the callback invokation code that the instance.load() call above
75976                 //makes here - ought to be able to normalize this - perhaps by caching at the Model.load layer
75977                 //instead of the association layer.
75978                 Ext.callback(options, scope, args);
75979                 Ext.callback(options.success, scope, args);
75980                 Ext.callback(options.failure, scope, args);
75981                 Ext.callback(options.callback, scope, args);
75982
75983                 return instance;
75984             }
75985         };
75986     },
75987
75988     /**
75989      * Read associated data
75990      * @private
75991      * @param {Ext.data.Model} record The record we're writing to
75992      * @param {Ext.data.reader.Reader} reader The reader for the associated model
75993      * @param {Object} associationData The raw associated data
75994      */
75995     read: function(record, reader, associationData){
75996         record[this.instanceName] = reader.read([associationData]).records[0];
75997     }
75998 });
75999
76000 /**
76001  * @class Ext.data.BufferStore
76002  * @extends Ext.data.Store
76003  * @ignore
76004  */
76005 Ext.define('Ext.data.BufferStore', {
76006     extend: 'Ext.data.Store',
76007     alias: 'store.buffer',
76008     sortOnLoad: false,
76009     filterOnLoad: false,
76010     
76011     constructor: function() {
76012         Ext.Error.raise('The BufferStore class has been deprecated. Instead, specify the buffered config option on Ext.data.Store');
76013     }
76014 });
76015 /**
76016  * Ext.Direct aims to streamline communication between the client and server by providing a single interface that
76017  * reduces the amount of common code typically required to validate data and handle returned data packets (reading data,
76018  * error conditions, etc).
76019  *
76020  * The Ext.direct namespace includes several classes for a closer integration with the server-side. The Ext.data
76021  * namespace also includes classes for working with Ext.data.Stores which are backed by data from an Ext.Direct method.
76022  *
76023  * # Specification
76024  *
76025  * For additional information consult the [Ext.Direct Specification][1].
76026  *
76027  * # Providers
76028  *
76029  * Ext.Direct uses a provider architecture, where one or more providers are used to transport data to and from the
76030  * server. There are several providers that exist in the core at the moment:
76031  *
76032  * - {@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations
76033  * - {@link Ext.direct.PollingProvider PollingProvider} for repeated requests
76034  * - {@link Ext.direct.RemotingProvider RemotingProvider} exposes server side on the client.
76035  *
76036  * A provider does not need to be invoked directly, providers are added via {@link Ext.direct.Manager}.{@link #addProvider}.
76037  *
76038  * # Router
76039  *
76040  * Ext.Direct utilizes a "router" on the server to direct requests from the client to the appropriate server-side
76041  * method. Because the Ext.Direct API is completely platform-agnostic, you could completely swap out a Java based server
76042  * solution and replace it with one that uses C# without changing the client side JavaScript at all.
76043  *
76044  * # Server side events
76045  *
76046  * Custom events from the server may be handled by the client by adding listeners, for example:
76047  *
76048  *     {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
76049  *
76050  *     // add a handler for a 'message' event sent by the server
76051  *     Ext.direct.Manager.on('message', function(e){
76052  *         out.append(String.format('<p><i>{0}</i></p>', e.data));
76053  *         out.el.scrollTo('t', 100000, true);
76054  *     });
76055  *
76056  *    [1]: http://sencha.com/products/extjs/extdirect
76057  *
76058  * @singleton
76059  * @alternateClassName Ext.Direct
76060  */
76061 Ext.define('Ext.direct.Manager', {
76062
76063     /* Begin Definitions */
76064     singleton: true,
76065
76066     mixins: {
76067         observable: 'Ext.util.Observable'
76068     },
76069
76070     requires: ['Ext.util.MixedCollection'],
76071
76072     statics: {
76073         exceptions: {
76074             TRANSPORT: 'xhr',
76075             PARSE: 'parse',
76076             LOGIN: 'login',
76077             SERVER: 'exception'
76078         }
76079     },
76080
76081     /* End Definitions */
76082
76083     constructor: function(){
76084         var me = this;
76085
76086         me.addEvents(
76087             /**
76088              * @event event
76089              * Fires after an event.
76090              * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
76091              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
76092              */
76093             'event',
76094             /**
76095              * @event exception
76096              * Fires after an event exception.
76097              * @param {Ext.direct.Event} e The event type that occurred.
76098              */
76099             'exception'
76100         );
76101         me.transactions = Ext.create('Ext.util.MixedCollection');
76102         me.providers = Ext.create('Ext.util.MixedCollection');
76103
76104         me.mixins.observable.constructor.call(me);
76105     },
76106
76107     /**
76108      * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods. If the provider
76109      * is not already connected, it will auto-connect.
76110      *
76111      *     var pollProv = new Ext.direct.PollingProvider({
76112      *         url: 'php/poll2.php'
76113      *     });
76114      *
76115      *     Ext.direct.Manager.addProvider({
76116      *         "type":"remoting",       // create a {@link Ext.direct.RemotingProvider}
76117      *         "url":"php\/router.php", // url to connect to the Ext.Direct server-side router.
76118      *         "actions":{              // each property within the actions object represents a Class
76119      *             "TestAction":[       // array of methods within each server side Class
76120      *             {
76121      *                 "name":"doEcho", // name of method
76122      *                 "len":1
76123      *             },{
76124      *                 "name":"multiply",
76125      *                 "len":1
76126      *             },{
76127      *                 "name":"doForm",
76128      *                 "formHandler":true, // handle form on server with Ext.Direct.Transaction
76129      *                 "len":1
76130      *             }]
76131      *         },
76132      *         "namespace":"myApplication",// namespace to create the Remoting Provider in
76133      *     },{
76134      *         type: 'polling', // create a {@link Ext.direct.PollingProvider}
76135      *         url:  'php/poll.php'
76136      *     }, pollProv); // reference to previously created instance
76137      *
76138      * @param {Ext.direct.Provider/Object...} provider
76139      * Accepts any number of Provider descriptions (an instance or config object for
76140      * a Provider). Each Provider description instructs Ext.Directhow to create
76141      * client-side stub methods.
76142      */
76143     addProvider : function(provider){
76144         var me = this,
76145             args = arguments,
76146             i = 0,
76147             len;
76148
76149         if (args.length > 1) {
76150             for (len = args.length; i < len; ++i) {
76151                 me.addProvider(args[i]);
76152             }
76153             return;
76154         }
76155
76156         // if provider has not already been instantiated
76157         if (!provider.isProvider) {
76158             provider = Ext.create('direct.' + provider.type + 'provider', provider);
76159         }
76160         me.providers.add(provider);
76161         provider.on('data', me.onProviderData, me);
76162
76163
76164         if (!provider.isConnected()) {
76165             provider.connect();
76166         }
76167
76168         return provider;
76169     },
76170
76171     /**
76172      * Retrieves a {@link Ext.direct.Provider provider} by the **{@link Ext.direct.Provider#id id}** specified when the
76173      * provider is {@link #addProvider added}.
76174      * @param {String/Ext.direct.Provider} id The id of the provider, or the provider instance.
76175      */
76176     getProvider : function(id){
76177         return id.isProvider ? id : this.providers.get(id);
76178     },
76179
76180     /**
76181      * Removes the provider.
76182      * @param {String/Ext.direct.Provider} provider The provider instance or the id of the provider.
76183      * @return {Ext.direct.Provider} The provider, null if not found.
76184      */
76185     removeProvider : function(provider){
76186         var me = this,
76187             providers = me.providers;
76188
76189         provider = provider.isProvider ? provider : providers.get(provider);
76190
76191         if (provider) {
76192             provider.un('data', me.onProviderData, me);
76193             providers.remove(provider);
76194             return provider;
76195         }
76196         return null;
76197     },
76198
76199     /**
76200      * Adds a transaction to the manager.
76201      * @private
76202      * @param {Ext.direct.Transaction} transaction The transaction to add
76203      * @return {Ext.direct.Transaction} transaction
76204      */
76205     addTransaction: function(transaction){
76206         this.transactions.add(transaction);
76207         return transaction;
76208     },
76209
76210     /**
76211      * Removes a transaction from the manager.
76212      * @private
76213      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to remove
76214      * @return {Ext.direct.Transaction} transaction
76215      */
76216     removeTransaction: function(transaction){
76217         transaction = this.getTransaction(transaction);
76218         this.transactions.remove(transaction);
76219         return transaction;
76220     },
76221
76222     /**
76223      * Gets a transaction
76224      * @private
76225      * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to get
76226      * @return {Ext.direct.Transaction}
76227      */
76228     getTransaction: function(transaction){
76229         return transaction.isTransaction ? transaction : this.transactions.get(transaction);
76230     },
76231
76232     onProviderData : function(provider, event){
76233         var me = this,
76234             i = 0,
76235             len;
76236
76237         if (Ext.isArray(event)) {
76238             for (len = event.length; i < len; ++i) {
76239                 me.onProviderData(provider, event[i]);
76240             }
76241             return;
76242         }
76243         if (event.name && event.name != 'event' && event.name != 'exception') {
76244             me.fireEvent(event.name, event);
76245         } else if (event.status === false) {
76246             me.fireEvent('exception', event);
76247         }
76248         me.fireEvent('event', event, provider);
76249     }
76250 }, function(){
76251     // Backwards compatibility
76252     Ext.Direct = Ext.direct.Manager;
76253 });
76254
76255 /**
76256  * This class is used to send requests to the server using {@link Ext.direct.Manager Ext.Direct}. When a
76257  * request is made, the transport mechanism is handed off to the appropriate
76258  * {@link Ext.direct.RemotingProvider Provider} to complete the call.
76259  *
76260  * # Specifying the function
76261  *
76262  * This proxy expects a Direct remoting method to be passed in order to be able to complete requests.
76263  * This can be done by specifying the {@link #directFn} configuration. This will use the same direct
76264  * method for all requests. Alternatively, you can provide an {@link #api} configuration. This
76265  * allows you to specify a different remoting method for each CRUD action.
76266  *
76267  * # Parameters
76268  *
76269  * This proxy provides options to help configure which parameters will be sent to the server.
76270  * By specifying the {@link #paramsAsHash} option, it will send an object literal containing each
76271  * of the passed parameters. The {@link #paramOrder} option can be used to specify the order in which
76272  * the remoting method parameters are passed.
76273  *
76274  * # Example Usage
76275  *
76276  *     Ext.define('User', {
76277  *         extend: 'Ext.data.Model',
76278  *         fields: ['firstName', 'lastName'],
76279  *         proxy: {
76280  *             type: 'direct',
76281  *             directFn: MyApp.getUsers,
76282  *             paramOrder: 'id' // Tells the proxy to pass the id as the first parameter to the remoting method.
76283  *         }
76284  *     });
76285  *     User.load(1);
76286  */
76287 Ext.define('Ext.data.proxy.Direct', {
76288     /* Begin Definitions */
76289
76290     extend: 'Ext.data.proxy.Server',
76291     alternateClassName: 'Ext.data.DirectProxy',
76292
76293     alias: 'proxy.direct',
76294
76295     requires: ['Ext.direct.Manager'],
76296
76297     /* End Definitions */
76298
76299     /**
76300      * @cfg {String/String[]} paramOrder
76301      * Defaults to undefined. A list of params to be executed server side.  Specify the params in the order in
76302      * which they must be executed on the server-side as either (1) an Array of String values, or (2) a String
76303      * of params delimited by either whitespace, comma, or pipe. For example, any of the following would be
76304      * acceptable:
76305      *
76306      *     paramOrder: ['param1','param2','param3']
76307      *     paramOrder: 'param1 param2 param3'
76308      *     paramOrder: 'param1,param2,param3'
76309      *     paramOrder: 'param1|param2|param'
76310      */
76311     paramOrder: undefined,
76312
76313     /**
76314      * @cfg {Boolean} paramsAsHash
76315      * Send parameters as a collection of named arguments.
76316      * Providing a {@link #paramOrder} nullifies this configuration.
76317      */
76318     paramsAsHash: true,
76319
76320     /**
76321      * @cfg {Function} directFn
76322      * Function to call when executing a request.  directFn is a simple alternative to defining the api configuration-parameter
76323      * for Store's which will not implement a full CRUD api.
76324      */
76325     directFn : undefined,
76326
76327     /**
76328      * @cfg {Object} api
76329      * The same as {@link Ext.data.proxy.Server#api}, however instead of providing urls, you should provide a direct
76330      * function call.
76331      */
76332
76333     /**
76334      * @cfg {Object} extraParams
76335      * Extra parameters that will be included on every read request. Individual requests with params
76336      * of the same name will override these params when they are in conflict.
76337      */
76338
76339     // private
76340     paramOrderRe: /[\s,|]/,
76341
76342     constructor: function(config){
76343         var me = this;
76344
76345         Ext.apply(me, config);
76346         if (Ext.isString(me.paramOrder)) {
76347             me.paramOrder = me.paramOrder.split(me.paramOrderRe);
76348         }
76349         me.callParent(arguments);
76350     },
76351
76352     doRequest: function(operation, callback, scope) {
76353         var me = this,
76354             writer = me.getWriter(),
76355             request = me.buildRequest(operation, callback, scope),
76356             fn = me.api[request.action]  || me.directFn,
76357             args = [],
76358             params = request.params,
76359             paramOrder = me.paramOrder,
76360             method,
76361             i = 0,
76362             len;
76363
76364
76365         if (operation.allowWrite()) {
76366             request = writer.write(request);
76367         }
76368
76369         if (operation.action == 'read') {
76370             // We need to pass params
76371             method = fn.directCfg.method;
76372
76373             if (method.ordered) {
76374                 if (method.len > 0) {
76375                     if (paramOrder) {
76376                         for (len = paramOrder.length; i < len; ++i) {
76377                             args.push(params[paramOrder[i]]);
76378                         }
76379                     } else if (me.paramsAsHash) {
76380                         args.push(params);
76381                     }
76382                 }
76383             } else {
76384                 args.push(params);
76385             }
76386         } else {
76387             args.push(request.jsonData);
76388         }
76389
76390         Ext.apply(request, {
76391             args: args,
76392             directFn: fn
76393         });
76394         args.push(me.createRequestCallback(request, operation, callback, scope), me);
76395         fn.apply(window, args);
76396     },
76397
76398     /*
76399      * Inherit docs. We don't apply any encoding here because
76400      * all of the direct requests go out as jsonData
76401      */
76402     applyEncoding: function(value){
76403         return value;
76404     },
76405
76406     createRequestCallback: function(request, operation, callback, scope){
76407         var me = this;
76408
76409         return function(data, event){
76410             me.processResponse(event.status, operation, request, event, callback, scope);
76411         };
76412     },
76413
76414     // inherit docs
76415     extractResponseData: function(response){
76416         return Ext.isDefined(response.result) ? response.result : response.data;
76417     },
76418
76419     // inherit docs
76420     setException: function(operation, response) {
76421         operation.setException(response.message);
76422     },
76423
76424     // inherit docs
76425     buildUrl: function(){
76426         return '';
76427     }
76428 });
76429
76430 /**
76431  * Small helper class to create an {@link Ext.data.Store} configured with an {@link Ext.data.proxy.Direct}
76432  * and {@link Ext.data.reader.Json} to make interacting with an {@link Ext.direct.Manager} server-side
76433  * {@link Ext.direct.Provider Provider} easier. To create a different proxy/reader combination create a basic
76434  * {@link Ext.data.Store} configured as needed.
76435  *
76436  * **Note:** Although they are not listed, this class inherits all of the config options of:
76437  *
76438  * - **{@link Ext.data.Store Store}**
76439  *
76440  * - **{@link Ext.data.reader.Json JsonReader}**
76441  *
76442  *   - **{@link Ext.data.reader.Json#root root}**
76443  *   - **{@link Ext.data.reader.Json#idProperty idProperty}**
76444  *   - **{@link Ext.data.reader.Json#totalProperty totalProperty}**
76445  *
76446  * - **{@link Ext.data.proxy.Direct DirectProxy}**
76447  *
76448  *   - **{@link Ext.data.proxy.Direct#directFn directFn}**
76449  *   - **{@link Ext.data.proxy.Direct#paramOrder paramOrder}**
76450  *   - **{@link Ext.data.proxy.Direct#paramsAsHash paramsAsHash}**
76451  *
76452  */
76453 Ext.define('Ext.data.DirectStore', {
76454     /* Begin Definitions */
76455     
76456     extend: 'Ext.data.Store',
76457     
76458     alias: 'store.direct',
76459     
76460     requires: ['Ext.data.proxy.Direct'],
76461    
76462     /* End Definitions */
76463
76464     constructor : function(config){
76465         config = Ext.apply({}, config);
76466         if (!config.proxy) {
76467             var proxy = {
76468                 type: 'direct',
76469                 reader: {
76470                     type: 'json'
76471                 }
76472             };
76473             Ext.copyTo(proxy, config, 'paramOrder,paramsAsHash,directFn,api,simpleSortMode');
76474             Ext.copyTo(proxy.reader, config, 'totalProperty,root,idProperty');
76475             config.proxy = proxy;
76476         }
76477         this.callParent([config]);
76478     }    
76479 });
76480
76481 /**
76482  * General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and
76483  * {@link #ordinalize ordinalizes} words. Sample usage:
76484  *
76485  *     //turning singular words into plurals
76486  *     Ext.util.Inflector.pluralize('word'); //'words'
76487  *     Ext.util.Inflector.pluralize('person'); //'people'
76488  *     Ext.util.Inflector.pluralize('sheep'); //'sheep'
76489  *
76490  *     //turning plurals into singulars
76491  *     Ext.util.Inflector.singularize('words'); //'word'
76492  *     Ext.util.Inflector.singularize('people'); //'person'
76493  *     Ext.util.Inflector.singularize('sheep'); //'sheep'
76494  *
76495  *     //ordinalizing numbers
76496  *     Ext.util.Inflector.ordinalize(11); //"11th"
76497  *     Ext.util.Inflector.ordinalize(21); //"21th"
76498  *     Ext.util.Inflector.ordinalize(1043); //"1043rd"
76499  *
76500  * # Customization
76501  *
76502  * The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
76503  * rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages.
76504  * Here is how we might add a rule that pluralizes "ox" to "oxen":
76505  *
76506  *     Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
76507  *
76508  * Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string. In
76509  * this case, the regular expression will only match the string "ox", and will replace that match with "oxen". Here's
76510  * how we could add the inverse rule:
76511  *
76512  *     Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
76513  *
76514  * Note that the ox/oxen rules are present by default.
76515  */
76516 Ext.define('Ext.util.Inflector', {
76517
76518     /* Begin Definitions */
76519
76520     singleton: true,
76521
76522     /* End Definitions */
76523
76524     /**
76525      * @private
76526      * The registered plural tuples. Each item in the array should contain two items - the first must be a regular
76527      * expression that matchers the singular form of a word, the second must be a String that replaces the matched
76528      * part of the regular expression. This is managed by the {@link #plural} method.
76529      * @property {Array} plurals
76530      */
76531     plurals: [
76532         [(/(quiz)$/i),                "$1zes"  ],
76533         [(/^(ox)$/i),                 "$1en"   ],
76534         [(/([m|l])ouse$/i),           "$1ice"  ],
76535         [(/(matr|vert|ind)ix|ex$/i),  "$1ices" ],
76536         [(/(x|ch|ss|sh)$/i),          "$1es"   ],
76537         [(/([^aeiouy]|qu)y$/i),       "$1ies"  ],
76538         [(/(hive)$/i),                "$1s"    ],
76539         [(/(?:([^f])fe|([lr])f)$/i),  "$1$2ves"],
76540         [(/sis$/i),                   "ses"    ],
76541         [(/([ti])um$/i),              "$1a"    ],
76542         [(/(buffal|tomat|potat)o$/i), "$1oes"  ],
76543         [(/(bu)s$/i),                 "$1ses"  ],
76544         [(/(alias|status|sex)$/i),    "$1es"   ],
76545         [(/(octop|vir)us$/i),         "$1i"    ],
76546         [(/(ax|test)is$/i),           "$1es"   ],
76547         [(/^person$/),                "people" ],
76548         [(/^man$/),                   "men"    ],
76549         [(/^(child)$/),               "$1ren"  ],
76550         [(/s$/i),                     "s"      ],
76551         [(/$/),                       "s"      ]
76552     ],
76553
76554     /**
76555      * @private
76556      * The set of registered singular matchers. Each item in the array should contain two items - the first must be a
76557      * regular expression that matches the plural form of a word, the second must be a String that replaces the
76558      * matched part of the regular expression. This is managed by the {@link #singular} method.
76559      * @property {Array} singulars
76560      */
76561     singulars: [
76562       [(/(quiz)zes$/i),                                                    "$1"     ],
76563       [(/(matr)ices$/i),                                                   "$1ix"   ],
76564       [(/(vert|ind)ices$/i),                                               "$1ex"   ],
76565       [(/^(ox)en/i),                                                       "$1"     ],
76566       [(/(alias|status)es$/i),                                             "$1"     ],
76567       [(/(octop|vir)i$/i),                                                 "$1us"   ],
76568       [(/(cris|ax|test)es$/i),                                             "$1is"   ],
76569       [(/(shoe)s$/i),                                                      "$1"     ],
76570       [(/(o)es$/i),                                                        "$1"     ],
76571       [(/(bus)es$/i),                                                      "$1"     ],
76572       [(/([m|l])ice$/i),                                                   "$1ouse" ],
76573       [(/(x|ch|ss|sh)es$/i),                                               "$1"     ],
76574       [(/(m)ovies$/i),                                                     "$1ovie" ],
76575       [(/(s)eries$/i),                                                     "$1eries"],
76576       [(/([^aeiouy]|qu)ies$/i),                                            "$1y"    ],
76577       [(/([lr])ves$/i),                                                    "$1f"    ],
76578       [(/(tive)s$/i),                                                      "$1"     ],
76579       [(/(hive)s$/i),                                                      "$1"     ],
76580       [(/([^f])ves$/i),                                                    "$1fe"   ],
76581       [(/(^analy)ses$/i),                                                  "$1sis"  ],
76582       [(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i), "$1$2sis"],
76583       [(/([ti])a$/i),                                                      "$1um"   ],
76584       [(/(n)ews$/i),                                                       "$1ews"  ],
76585       [(/people$/i),                                                       "person" ],
76586       [(/s$/i),                                                            ""       ]
76587     ],
76588
76589     /**
76590      * @private
76591      * The registered uncountable words
76592      * @property {String[]} uncountable
76593      */
76594      uncountable: [
76595         "sheep",
76596         "fish",
76597         "series",
76598         "species",
76599         "money",
76600         "rice",
76601         "information",
76602         "equipment",
76603         "grass",
76604         "mud",
76605         "offspring",
76606         "deer",
76607         "means"
76608     ],
76609
76610     /**
76611      * Adds a new singularization rule to the Inflector. See the intro docs for more information
76612      * @param {RegExp} matcher The matcher regex
76613      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
76614      */
76615     singular: function(matcher, replacer) {
76616         this.singulars.unshift([matcher, replacer]);
76617     },
76618
76619     /**
76620      * Adds a new pluralization rule to the Inflector. See the intro docs for more information
76621      * @param {RegExp} matcher The matcher regex
76622      * @param {String} replacer The replacement string, which can reference matches from the matcher argument
76623      */
76624     plural: function(matcher, replacer) {
76625         this.plurals.unshift([matcher, replacer]);
76626     },
76627
76628     /**
76629      * Removes all registered singularization rules
76630      */
76631     clearSingulars: function() {
76632         this.singulars = [];
76633     },
76634
76635     /**
76636      * Removes all registered pluralization rules
76637      */
76638     clearPlurals: function() {
76639         this.plurals = [];
76640     },
76641
76642     /**
76643      * Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish)
76644      * @param {String} word The word to test
76645      * @return {Boolean} True if the word is transnumeral
76646      */
76647     isTransnumeral: function(word) {
76648         return Ext.Array.indexOf(this.uncountable, word) != -1;
76649     },
76650
76651     /**
76652      * Returns the pluralized form of a word (e.g. Ext.util.Inflector.pluralize('word') returns 'words')
76653      * @param {String} word The word to pluralize
76654      * @return {String} The pluralized form of the word
76655      */
76656     pluralize: function(word) {
76657         if (this.isTransnumeral(word)) {
76658             return word;
76659         }
76660
76661         var plurals = this.plurals,
76662             length  = plurals.length,
76663             tuple, regex, i;
76664
76665         for (i = 0; i < length; i++) {
76666             tuple = plurals[i];
76667             regex = tuple[0];
76668
76669             if (regex == word || (regex.test && regex.test(word))) {
76670                 return word.replace(regex, tuple[1]);
76671             }
76672         }
76673
76674         return word;
76675     },
76676
76677     /**
76678      * Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word')
76679      * @param {String} word The word to singularize
76680      * @return {String} The singularized form of the word
76681      */
76682     singularize: function(word) {
76683         if (this.isTransnumeral(word)) {
76684             return word;
76685         }
76686
76687         var singulars = this.singulars,
76688             length    = singulars.length,
76689             tuple, regex, i;
76690
76691         for (i = 0; i < length; i++) {
76692             tuple = singulars[i];
76693             regex = tuple[0];
76694
76695             if (regex == word || (regex.test && regex.test(word))) {
76696                 return word.replace(regex, tuple[1]);
76697             }
76698         }
76699
76700         return word;
76701     },
76702
76703     /**
76704      * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data
76705      * package
76706      * @param {String} word The word to classify
76707      * @return {String} The classified version of the word
76708      */
76709     classify: function(word) {
76710         return Ext.String.capitalize(this.singularize(word));
76711     },
76712
76713     /**
76714      * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the
76715      * number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc
76716      * @param {Number} number The number to ordinalize
76717      * @return {String} The ordinalized number
76718      */
76719     ordinalize: function(number) {
76720         var parsed = parseInt(number, 10),
76721             mod10  = parsed % 10,
76722             mod100 = parsed % 100;
76723
76724         //11 through 13 are a special case
76725         if (11 <= mod100 && mod100 <= 13) {
76726             return number + "th";
76727         } else {
76728             switch(mod10) {
76729                 case 1 : return number + "st";
76730                 case 2 : return number + "nd";
76731                 case 3 : return number + "rd";
76732                 default: return number + "th";
76733             }
76734         }
76735     }
76736 }, function() {
76737     //aside from the rules above, there are a number of words that have irregular pluralization so we add them here
76738     var irregulars = {
76739             alumnus: 'alumni',
76740             cactus : 'cacti',
76741             focus  : 'foci',
76742             nucleus: 'nuclei',
76743             radius: 'radii',
76744             stimulus: 'stimuli',
76745             ellipsis: 'ellipses',
76746             paralysis: 'paralyses',
76747             oasis: 'oases',
76748             appendix: 'appendices',
76749             index: 'indexes',
76750             beau: 'beaux',
76751             bureau: 'bureaux',
76752             tableau: 'tableaux',
76753             woman: 'women',
76754             child: 'children',
76755             man: 'men',
76756             corpus:     'corpora',
76757             criterion: 'criteria',
76758             curriculum: 'curricula',
76759             genus: 'genera',
76760             memorandum: 'memoranda',
76761             phenomenon: 'phenomena',
76762             foot: 'feet',
76763             goose: 'geese',
76764             tooth: 'teeth',
76765             antenna: 'antennae',
76766             formula: 'formulae',
76767             nebula: 'nebulae',
76768             vertebra: 'vertebrae',
76769             vita: 'vitae'
76770         },
76771         singular;
76772
76773     for (singular in irregulars) {
76774         this.plural(singular, irregulars[singular]);
76775         this.singular(irregulars[singular], singular);
76776     }
76777 });
76778 /**
76779  * @author Ed Spencer
76780  * @class Ext.data.HasManyAssociation
76781  * @extends Ext.data.Association
76782  * 
76783  * <p>Represents a one-to-many relationship between two models. Usually created indirectly via a model definition:</p>
76784  * 
76785 <pre><code>
76786 Ext.define('Product', {
76787     extend: 'Ext.data.Model',
76788     fields: [
76789         {name: 'id',      type: 'int'},
76790         {name: 'user_id', type: 'int'},
76791         {name: 'name',    type: 'string'}
76792     ]
76793 });
76794
76795 Ext.define('User', {
76796     extend: 'Ext.data.Model',
76797     fields: [
76798         {name: 'id',   type: 'int'},
76799         {name: 'name', type: 'string'}
76800     ],
76801     // we can use the hasMany shortcut on the model to create a hasMany association
76802     hasMany: {model: 'Product', name: 'products'}
76803 });
76804 </pre></code>
76805
76806  * <p>Above we created Product and User models, and linked them by saying that a User hasMany Products. This gives
76807  * us a new function on every User instance, in this case the function is called 'products' because that is the name
76808  * we specified in the association configuration above.</p>
76809  * 
76810  * <p>This new function returns a specialized {@link Ext.data.Store Store} which is automatically filtered to load
76811  * only Products for the given model instance:</p>
76812  * 
76813 <pre><code>
76814 //first, we load up a User with id of 1
76815 var user = Ext.create('User', {id: 1, name: 'Ed'});
76816
76817 //the user.products function was created automatically by the association and returns a {@link Ext.data.Store Store}
76818 //the created store is automatically scoped to the set of Products for the User with id of 1
76819 var products = user.products();
76820
76821 //we still have all of the usual Store functions, for example it's easy to add a Product for this User
76822 products.add({
76823     name: 'Another Product'
76824 });
76825
76826 //saves the changes to the store - this automatically sets the new Product's user_id to 1 before saving
76827 products.sync();
76828 </code></pre>
76829  * 
76830  * <p>The new Store is only instantiated the first time you call products() to conserve memory and processing time,
76831  * though calling products() a second time returns the same store instance.</p>
76832  * 
76833  * <p><u>Custom filtering</u></p>
76834  * 
76835  * <p>The Store is automatically furnished with a filter - by default this filter tells the store to only return
76836  * records where the associated model's foreign key matches the owner model's primary key. For example, if a User
76837  * with ID = 100 hasMany Products, the filter loads only Products with user_id == 100.</p>
76838  * 
76839  * <p>Sometimes we want to filter by another field - for example in the case of a Twitter search application we may
76840  * have models for Search and Tweet:</p>
76841  * 
76842 <pre><code>
76843 Ext.define('Search', {
76844     extend: 'Ext.data.Model',
76845     fields: [
76846         'id', 'query'
76847     ],
76848
76849     hasMany: {
76850         model: 'Tweet',
76851         name : 'tweets',
76852         filterProperty: 'query'
76853     }
76854 });
76855
76856 Ext.define('Tweet', {
76857     extend: 'Ext.data.Model',
76858     fields: [
76859         'id', 'text', 'from_user'
76860     ]
76861 });
76862
76863 //returns a Store filtered by the filterProperty
76864 var store = new Search({query: 'Sencha Touch'}).tweets();
76865 </code></pre>
76866  * 
76867  * <p>The tweets association above is filtered by the query property by setting the {@link #filterProperty}, and is
76868  * equivalent to this:</p>
76869  * 
76870 <pre><code>
76871 var store = Ext.create('Ext.data.Store', {
76872     model: 'Tweet',
76873     filters: [
76874         {
76875             property: 'query',
76876             value   : 'Sencha Touch'
76877         }
76878     ]
76879 });
76880 </code></pre>
76881  */
76882 Ext.define('Ext.data.HasManyAssociation', {
76883     extend: 'Ext.data.Association',
76884     requires: ['Ext.util.Inflector'],
76885
76886     alias: 'association.hasmany',
76887
76888     /**
76889      * @cfg {String} foreignKey The name of the foreign key on the associated model that links it to the owner
76890      * model. Defaults to the lowercased name of the owner model plus "_id", e.g. an association with a where a
76891      * model called Group hasMany Users would create 'group_id' as the foreign key. When the remote store is loaded,
76892      * the store is automatically filtered so that only records with a matching foreign key are included in the 
76893      * resulting child store. This can be overridden by specifying the {@link #filterProperty}.
76894      * <pre><code>
76895 Ext.define('Group', {
76896     extend: 'Ext.data.Model',
76897     fields: ['id', 'name'],
76898     hasMany: 'User'
76899 });
76900
76901 Ext.define('User', {
76902     extend: 'Ext.data.Model',
76903     fields: ['id', 'name', 'group_id'], // refers to the id of the group that this user belongs to
76904     belongsTo: 'Group'
76905 });
76906      * </code></pre>
76907      */
76908     
76909     /**
76910      * @cfg {String} name The name of the function to create on the owner model to retrieve the child store.
76911      * If not specified, the pluralized name of the child model is used.
76912      * <pre><code>
76913 // This will create a users() method on any Group model instance
76914 Ext.define('Group', {
76915     extend: 'Ext.data.Model',
76916     fields: ['id', 'name'],
76917     hasMany: 'User'
76918 });
76919 var group = new Group();
76920 console.log(group.users());
76921
76922 // The method to retrieve the users will now be getUserList
76923 Ext.define('Group', {
76924     extend: 'Ext.data.Model',
76925     fields: ['id', 'name'],
76926     hasMany: {model: 'User', name: 'getUserList'}
76927 });
76928 var group = new Group();
76929 console.log(group.getUserList());
76930      * </code></pre>
76931      */
76932     
76933     /**
76934      * @cfg {Object} storeConfig Optional configuration object that will be passed to the generated Store. Defaults to 
76935      * undefined.
76936      */
76937     
76938     /**
76939      * @cfg {String} filterProperty Optionally overrides the default filter that is set up on the associated Store. If
76940      * this is not set, a filter is automatically created which filters the association based on the configured 
76941      * {@link #foreignKey}. See intro docs for more details. Defaults to undefined
76942      */
76943     
76944     /**
76945      * @cfg {Boolean} autoLoad True to automatically load the related store from a remote source when instantiated.
76946      * Defaults to <tt>false</tt>.
76947      */
76948     
76949     /**
76950      * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
76951      * Use 'hasMany' to create a HasManyAssocation
76952      * <pre><code>
76953 associations: [{
76954     type: 'hasMany',
76955     model: 'User'
76956 }]
76957      * </code></pre>
76958      */
76959     
76960     constructor: function(config) {
76961         var me = this,
76962             ownerProto,
76963             name;
76964             
76965         me.callParent(arguments);
76966         
76967         me.name = me.name || Ext.util.Inflector.pluralize(me.associatedName.toLowerCase());
76968         
76969         ownerProto = me.ownerModel.prototype;
76970         name = me.name;
76971         
76972         Ext.applyIf(me, {
76973             storeName : name + "Store",
76974             foreignKey: me.ownerName.toLowerCase() + "_id"
76975         });
76976         
76977         ownerProto[name] = me.createStore();
76978     },
76979     
76980     /**
76981      * @private
76982      * Creates a function that returns an Ext.data.Store which is configured to load a set of data filtered
76983      * by the owner model's primary key - e.g. in a hasMany association where Group hasMany Users, this function
76984      * returns a Store configured to return the filtered set of a single Group's Users.
76985      * @return {Function} The store-generating function
76986      */
76987     createStore: function() {
76988         var that            = this,
76989             associatedModel = that.associatedModel,
76990             storeName       = that.storeName,
76991             foreignKey      = that.foreignKey,
76992             primaryKey      = that.primaryKey,
76993             filterProperty  = that.filterProperty,
76994             autoLoad        = that.autoLoad,
76995             storeConfig     = that.storeConfig || {};
76996         
76997         return function() {
76998             var me = this,
76999                 config, filter,
77000                 modelDefaults = {};
77001                 
77002             if (me[storeName] === undefined) {
77003                 if (filterProperty) {
77004                     filter = {
77005                         property  : filterProperty,
77006                         value     : me.get(filterProperty),
77007                         exactMatch: true
77008                     };
77009                 } else {
77010                     filter = {
77011                         property  : foreignKey,
77012                         value     : me.get(primaryKey),
77013                         exactMatch: true
77014                     };
77015                 }
77016                 
77017                 modelDefaults[foreignKey] = me.get(primaryKey);
77018                 
77019                 config = Ext.apply({}, storeConfig, {
77020                     model        : associatedModel,
77021                     filters      : [filter],
77022                     remoteFilter : false,
77023                     modelDefaults: modelDefaults
77024                 });
77025                 
77026                 me[storeName] = Ext.create('Ext.data.Store', config);
77027                 if (autoLoad) {
77028                     me[storeName].load();
77029                 }
77030             }
77031             
77032             return me[storeName];
77033         };
77034     },
77035     
77036     /**
77037      * Read associated data
77038      * @private
77039      * @param {Ext.data.Model} record The record we're writing to
77040      * @param {Ext.data.reader.Reader} reader The reader for the associated model
77041      * @param {Object} associationData The raw associated data
77042      */
77043     read: function(record, reader, associationData){
77044         var store = record[this.name](),
77045             inverse;
77046     
77047         store.add(reader.read(associationData).records);
77048     
77049         //now that we've added the related records to the hasMany association, set the inverse belongsTo
77050         //association on each of them if it exists
77051         inverse = this.associatedModel.prototype.associations.findBy(function(assoc){
77052             return assoc.type === 'belongsTo' && assoc.associatedName === record.$className;
77053         });
77054     
77055         //if the inverse association was found, set it now on each record we've just created
77056         if (inverse) {
77057             store.data.each(function(associatedRecord){
77058                 associatedRecord[inverse.instanceName] = record;
77059             });
77060         }
77061     }
77062 });
77063 /**
77064  * @class Ext.data.JsonP
77065  * @singleton
77066  * This class is used to create JSONP requests. JSONP is a mechanism that allows for making
77067  * requests for data cross domain. More information is available <a href="http://en.wikipedia.org/wiki/JSONP">here</a>.
77068  */
77069 Ext.define('Ext.data.JsonP', {
77070
77071     /* Begin Definitions */
77072
77073     singleton: true,
77074
77075     statics: {
77076         requestCount: 0,
77077         requests: {}
77078     },
77079
77080     /* End Definitions */
77081
77082     /**
77083      * @property timeout
77084      * @type Number
77085      * A default timeout for any JsonP requests. If the request has not completed in this time the
77086      * failure callback will be fired. The timeout is in ms. Defaults to <tt>30000</tt>.
77087      */
77088     timeout: 30000,
77089
77090     /**
77091      * @property disableCaching
77092      * @type Boolean
77093      * True to add a unique cache-buster param to requests. Defaults to <tt>true</tt>.
77094      */
77095     disableCaching: true,
77096
77097     /**
77098      * @property disableCachingParam
77099      * @type String
77100      * Change the parameter which is sent went disabling caching through a cache buster. Defaults to <tt>'_dc'</tt>.
77101      */
77102     disableCachingParam: '_dc',
77103
77104     /**
77105      * @property callbackKey
77106      * @type String
77107      * Specifies the GET parameter that will be sent to the server containing the function name to be executed when
77108      * the request completes. Defaults to <tt>callback</tt>. Thus, a common request will be in the form of
77109      * url?callback=Ext.data.JsonP.callback1
77110      */
77111     callbackKey: 'callback',
77112
77113     /**
77114      * Makes a JSONP request.
77115      * @param {Object} options An object which may contain the following properties. Note that options will
77116      * take priority over any defaults that are specified in the class.
77117      * <ul>
77118      * <li><b>url</b> : String <div class="sub-desc">The URL to request.</div></li>
77119      * <li><b>params</b> : Object (Optional)<div class="sub-desc">An object containing a series of
77120      * key value pairs that will be sent along with the request.</div></li>
77121      * <li><b>timeout</b> : Number (Optional) <div class="sub-desc">See {@link #timeout}</div></li>
77122      * <li><b>callbackKey</b> : String (Optional) <div class="sub-desc">See {@link #callbackKey}</div></li>
77123      * <li><b>callbackName</b> : String (Optional) <div class="sub-desc">The function name to use for this request.
77124      * By default this name will be auto-generated: Ext.data.JsonP.callback1, Ext.data.JsonP.callback2, etc.
77125      * Setting this option to "my_name" will force the function name to be Ext.data.JsonP.my_name.
77126      * Use this if you want deterministic behavior, but be careful - the callbackName should be different
77127      * in each JsonP request that you make.</div></li>
77128      * <li><b>disableCaching</b> : Boolean (Optional) <div class="sub-desc">See {@link #disableCaching}</div></li>
77129      * <li><b>disableCachingParam</b> : String (Optional) <div class="sub-desc">See {@link #disableCachingParam}</div></li>
77130      * <li><b>success</b> : Function (Optional) <div class="sub-desc">A function to execute if the request succeeds.</div></li>
77131      * <li><b>failure</b> : Function (Optional) <div class="sub-desc">A function to execute if the request fails.</div></li>
77132      * <li><b>callback</b> : Function (Optional) <div class="sub-desc">A function to execute when the request
77133      * completes, whether it is a success or failure.</div></li>
77134      * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
77135      * which to execute the callbacks: The "this" object for the callback function. Defaults to the browser window.</div></li>
77136      * </ul>
77137      * @return {Object} request An object containing the request details.
77138      */
77139     request: function(options){
77140         options = Ext.apply({}, options);
77141
77142
77143         var me = this,
77144             disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching,
77145             cacheParam = options.disableCachingParam || me.disableCachingParam,
77146             id = ++me.statics().requestCount,
77147             callbackName = options.callbackName || 'callback' + id,
77148             callbackKey = options.callbackKey || me.callbackKey,
77149             timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout,
77150             params = Ext.apply({}, options.params),
77151             url = options.url,
77152             name = Ext.isSandboxed ? Ext.getUniqueGlobalNamespace() : 'Ext',
77153             request,
77154             script;
77155
77156         params[callbackKey] = name + '.data.JsonP.' + callbackName;
77157         if (disableCaching) {
77158             params[cacheParam] = new Date().getTime();
77159         }
77160
77161         script = me.createScript(url, params);
77162
77163         me.statics().requests[id] = request = {
77164             url: url,
77165             params: params,
77166             script: script,
77167             id: id,
77168             scope: options.scope,
77169             success: options.success,
77170             failure: options.failure,
77171             callback: options.callback,
77172             callbackName: callbackName
77173         };
77174
77175         if (timeout > 0) {
77176             request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout);
77177         }
77178
77179         me.setupErrorHandling(request);
77180         me[callbackName] = Ext.bind(me.handleResponse, me, [request], true);
77181         Ext.getHead().appendChild(script);
77182         return request;
77183     },
77184
77185     /**
77186      * Abort a request. If the request parameter is not specified all open requests will
77187      * be aborted.
77188      * @param {Object/String} request (Optional) The request to abort
77189      */
77190     abort: function(request){
77191         var requests = this.statics().requests,
77192             key;
77193
77194         if (request) {
77195             if (!request.id) {
77196                 request = requests[request];
77197             }
77198             this.abort(request);
77199         } else {
77200             for (key in requests) {
77201                 if (requests.hasOwnProperty(key)) {
77202                     this.abort(requests[key]);
77203                 }
77204             }
77205         }
77206     },
77207
77208     /**
77209      * Sets up error handling for the script
77210      * @private
77211      * @param {Object} request The request
77212      */
77213     setupErrorHandling: function(request){
77214         request.script.onerror = Ext.bind(this.handleError, this, [request]);
77215     },
77216
77217     /**
77218      * Handles any aborts when loading the script
77219      * @private
77220      * @param {Object} request The request
77221      */
77222     handleAbort: function(request){
77223         request.errorType = 'abort';
77224         this.handleResponse(null, request);
77225     },
77226
77227     /**
77228      * Handles any script errors when loading the script
77229      * @private
77230      * @param {Object} request The request
77231      */
77232     handleError: function(request){
77233         request.errorType = 'error';
77234         this.handleResponse(null, request);
77235     },
77236
77237     /**
77238      * Cleans up anu script handling errors
77239      * @private
77240      * @param {Object} request The request
77241      */
77242     cleanupErrorHandling: function(request){
77243         request.script.onerror = null;
77244     },
77245
77246     /**
77247      * Handle any script timeouts
77248      * @private
77249      * @param {Object} request The request
77250      */
77251     handleTimeout: function(request){
77252         request.errorType = 'timeout';
77253         this.handleResponse(null, request);
77254     },
77255
77256     /**
77257      * Handle a successful response
77258      * @private
77259      * @param {Object} result The result from the request
77260      * @param {Object} request The request
77261      */
77262     handleResponse: function(result, request){
77263
77264         var success = true;
77265
77266         if (request.timeout) {
77267             clearTimeout(request.timeout);
77268         }
77269         delete this[request.callbackName];
77270         delete this.statics()[request.id];
77271         this.cleanupErrorHandling(request);
77272         Ext.fly(request.script).remove();
77273
77274         if (request.errorType) {
77275             success = false;
77276             Ext.callback(request.failure, request.scope, [request.errorType]);
77277         } else {
77278             Ext.callback(request.success, request.scope, [result]);
77279         }
77280         Ext.callback(request.callback, request.scope, [success, result, request.errorType]);
77281     },
77282
77283     /**
77284      * Create the script tag
77285      * @private
77286      * @param {String} url The url of the request
77287      * @param {Object} params Any extra params to be sent
77288      */
77289     createScript: function(url, params) {
77290         var script = document.createElement('script');
77291         script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params)));
77292         script.setAttribute("async", true);
77293         script.setAttribute("type", "text/javascript");
77294         return script;
77295     }
77296 });
77297
77298 /**
77299  * @class Ext.data.JsonPStore
77300  * @extends Ext.data.Store
77301  * @private
77302  * <p>Small helper class to make creating {@link Ext.data.Store}s from different domain JSON data easier.
77303  * A JsonPStore will be automatically configured with a {@link Ext.data.reader.Json} and a {@link Ext.data.proxy.JsonP JsonPProxy}.</p>
77304  * <p>A store configuration would be something like:<pre><code>
77305 var store = new Ext.data.JsonPStore({
77306     // store configs
77307     autoDestroy: true,
77308     storeId: 'myStore',
77309
77310     // proxy configs
77311     url: 'get-images.php',
77312
77313     // reader configs
77314     root: 'images',
77315     idProperty: 'name',
77316     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
77317 });
77318  * </code></pre></p>
77319  * <p>This store is configured to consume a returned object of the form:<pre><code>
77320 stcCallback({
77321     images: [
77322         {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
77323         {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
77324     ]
77325 })
77326  * </code></pre>
77327  * <p>Where stcCallback is the callback name passed in the request to the remote domain. See {@link Ext.data.proxy.JsonP JsonPProxy}
77328  * for details of how this works.</p>
77329  * An object literal of this form could also be used as the {@link #data} config option.</p>
77330  * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
77331  * <b>{@link Ext.data.reader.Json JsonReader}</b> and <b>{@link Ext.data.proxy.JsonP JsonPProxy}</b>.</p>
77332  * @xtype jsonpstore
77333  */
77334 Ext.define('Ext.data.JsonPStore', {
77335     extend: 'Ext.data.Store',
77336     alias : 'store.jsonp',
77337
77338     /**
77339      * @cfg {Ext.data.DataReader} reader @hide
77340      */
77341     constructor: function(config) {
77342         this.callParent(Ext.apply(config, {
77343             reader: Ext.create('Ext.data.reader.Json', config),
77344             proxy : Ext.create('Ext.data.proxy.JsonP', config)
77345         }));
77346     }
77347 });
77348
77349 /**
77350  * This class is used as a set of methods that are applied to the prototype of a
77351  * Model to decorate it with a Node API. This means that models used in conjunction with a tree
77352  * will have all of the tree related methods available on the model. In general this class will
77353  * not be used directly by the developer. This class also creates extra fields on the model if
77354  * they do not exist, to help maintain the tree state and UI. These fields are documented as
77355  * config options.
77356  */
77357 Ext.define('Ext.data.NodeInterface', {
77358     requires: ['Ext.data.Field'],
77359
77360     /**
77361      * @cfg {String} parentId
77362      * ID of parent node.
77363      */
77364
77365     /**
77366      * @cfg {Number} index
77367      * The position of the node inside its parent. When parent has 4 children and the node is third amongst them,
77368      * index will be 2.
77369      */
77370
77371     /**
77372      * @cfg {Number} depth
77373      * The number of parents this node has. A root node has depth 0, a child of it depth 1, and so on...
77374      */
77375
77376     /**
77377      * @cfg {Boolean} [expanded=false]
77378      * True if the node is expanded.
77379      */
77380
77381     /**
77382      * @cfg {Boolean} [expandable=false]
77383      * Set to true to allow for expanding/collapsing of this node.
77384      */
77385
77386     /**
77387      * @cfg {Boolean} [checked=null]
77388      * Set to true or false to show a checkbox alongside this node.
77389      */
77390
77391     /**
77392      * @cfg {Boolean} [leaf=false]
77393      * Set to true to indicate that this child can have no children. The expand icon/arrow will then not be
77394      * rendered for this node.
77395      */
77396
77397     /**
77398      * @cfg {String} cls
77399      * CSS class to apply for this node.
77400      */
77401
77402     /**
77403      * @cfg {String} iconCls
77404      * CSS class to apply for this node's icon.
77405      */
77406
77407     /**
77408      * @cfg {String} icon
77409      * URL for this node's icon.
77410      */
77411
77412     /**
77413      * @cfg {Boolean} root
77414      * True if this is the root node.
77415      */
77416
77417     /**
77418      * @cfg {Boolean} isLast
77419      * True if this is the last node.
77420      */
77421
77422     /**
77423      * @cfg {Boolean} isFirst
77424      * True if this is the first node.
77425      */
77426
77427     /**
77428      * @cfg {Boolean} [allowDrop=true]
77429      * Set to false to deny dropping on this node.
77430      */
77431
77432     /**
77433      * @cfg {Boolean} [allowDrag=true]
77434      * Set to false to deny dragging of this node.
77435      */
77436
77437     /**
77438      * @cfg {Boolean} [loaded=false]
77439      * True if the node has finished loading.
77440      */
77441
77442     /**
77443      * @cfg {Boolean} [loading=false]
77444      * True if the node is currently loading.
77445      */
77446
77447     /**
77448      * @cfg {String} href
77449      * An URL for a link that's created when this config is specified.
77450      */
77451
77452     /**
77453      * @cfg {String} hrefTarget
77454      * Target for link. Only applicable when {@link #href} also specified.
77455      */
77456
77457     /**
77458      * @cfg {String} qtip
77459      * Tooltip text to show on this node.
77460      */
77461
77462     /**
77463      * @cfg {String} qtitle
77464      * Tooltip title.
77465      */
77466
77467     /**
77468      * @cfg {String} text
77469      * The text for to show on node label.
77470      */
77471
77472     /**
77473      * @cfg {Ext.data.NodeInterface[]} children
77474      * Array of child nodes.
77475      */
77476
77477
77478     /**
77479      * @property nextSibling
77480      * A reference to this node's next sibling node. `null` if this node does not have a next sibling.
77481      */
77482
77483     /**
77484      * @property previousSibling
77485      * A reference to this node's previous sibling node. `null` if this node does not have a previous sibling.
77486      */
77487
77488     /**
77489      * @property parentNode
77490      * A reference to this node's parent node. `null` if this node is the root node.
77491      */
77492
77493     /**
77494      * @property lastChild
77495      * A reference to this node's last child node. `null` if this node has no children.
77496      */
77497
77498     /**
77499      * @property firstChild
77500      * A reference to this node's first child node. `null` if this node has no children.
77501      */
77502
77503     /**
77504      * @property childNodes
77505      * An array of this nodes children.  Array will be empty if this node has no chidren.
77506      */
77507
77508     statics: {
77509         /**
77510          * This method allows you to decorate a Record's prototype to implement the NodeInterface.
77511          * This adds a set of methods, new events, new properties and new fields on every Record
77512          * with the same Model as the passed Record.
77513          * @param {Ext.data.Model} record The Record you want to decorate the prototype of.
77514          * @static
77515          */
77516         decorate: function(record) {
77517             if (!record.isNode) {
77518                 // Apply the methods and fields to the prototype
77519                 // @TODO: clean this up to use proper class system stuff
77520                 var mgr = Ext.ModelManager,
77521                     modelName = record.modelName,
77522                     modelClass = mgr.getModel(modelName),
77523                     idName = modelClass.prototype.idProperty,
77524                     newFields = [],
77525                     i, newField, len;
77526
77527                 // Start by adding the NodeInterface methods to the Model's prototype
77528                 modelClass.override(this.getPrototypeBody());
77529                 newFields = this.applyFields(modelClass, [
77530                     {name: idName,       type: 'string',  defaultValue: null},
77531                     {name: 'parentId',   type: 'string',  defaultValue: null},
77532                     {name: 'index',      type: 'int',     defaultValue: null},
77533                     {name: 'depth',      type: 'int',     defaultValue: 0},
77534                     {name: 'expanded',   type: 'bool',    defaultValue: false, persist: false},
77535                     {name: 'expandable', type: 'bool',    defaultValue: true, persist: false},
77536                     {name: 'checked',    type: 'auto',    defaultValue: null},
77537                     {name: 'leaf',       type: 'bool',    defaultValue: false, persist: false},
77538                     {name: 'cls',        type: 'string',  defaultValue: null, persist: false},
77539                     {name: 'iconCls',    type: 'string',  defaultValue: null, persist: false},
77540                     {name: 'icon',       type: 'string',  defaultValue: null, persist: false},
77541                     {name: 'root',       type: 'boolean', defaultValue: false, persist: false},
77542                     {name: 'isLast',     type: 'boolean', defaultValue: false, persist: false},
77543                     {name: 'isFirst',    type: 'boolean', defaultValue: false, persist: false},
77544                     {name: 'allowDrop',  type: 'boolean', defaultValue: true, persist: false},
77545                     {name: 'allowDrag',  type: 'boolean', defaultValue: true, persist: false},
77546                     {name: 'loaded',     type: 'boolean', defaultValue: false, persist: false},
77547                     {name: 'loading',    type: 'boolean', defaultValue: false, persist: false},
77548                     {name: 'href',       type: 'string',  defaultValue: null, persist: false},
77549                     {name: 'hrefTarget', type: 'string',  defaultValue: null, persist: false},
77550                     {name: 'qtip',       type: 'string',  defaultValue: null, persist: false},
77551                     {name: 'qtitle',     type: 'string',  defaultValue: null, persist: false}
77552                 ]);
77553
77554                 len = newFields.length;
77555                 // Set default values
77556                 for (i = 0; i < len; ++i) {
77557                     newField = newFields[i];
77558                     if (record.get(newField.name) === undefined) {
77559                         record.data[newField.name] = newField.defaultValue;
77560                     }
77561                 }
77562             }
77563
77564             Ext.applyIf(record, {
77565                 firstChild: null,
77566                 lastChild: null,
77567                 parentNode: null,
77568                 previousSibling: null,
77569                 nextSibling: null,
77570                 childNodes: []
77571             });
77572             // Commit any fields so the record doesn't show as dirty initially
77573             record.commit(true);
77574
77575             record.enableBubble([
77576                 /**
77577                  * @event append
77578                  * Fires when a new child node is appended
77579                  * @param {Ext.data.NodeInterface} this This node
77580                  * @param {Ext.data.NodeInterface} node The newly appended node
77581                  * @param {Number} index The index of the newly appended node
77582                  */
77583                 "append",
77584
77585                 /**
77586                  * @event remove
77587                  * Fires when a child node is removed
77588                  * @param {Ext.data.NodeInterface} this This node
77589                  * @param {Ext.data.NodeInterface} node The removed node
77590                  */
77591                 "remove",
77592
77593                 /**
77594                  * @event move
77595                  * Fires when this node is moved to a new location in the tree
77596                  * @param {Ext.data.NodeInterface} this This node
77597                  * @param {Ext.data.NodeInterface} oldParent The old parent of this node
77598                  * @param {Ext.data.NodeInterface} newParent The new parent of this node
77599                  * @param {Number} index The index it was moved to
77600                  */
77601                 "move",
77602
77603                 /**
77604                  * @event insert
77605                  * Fires when a new child node is inserted.
77606                  * @param {Ext.data.NodeInterface} this This node
77607                  * @param {Ext.data.NodeInterface} node The child node inserted
77608                  * @param {Ext.data.NodeInterface} refNode The child node the node was inserted before
77609                  */
77610                 "insert",
77611
77612                 /**
77613                  * @event beforeappend
77614                  * Fires before a new child is appended, return false to cancel the append.
77615                  * @param {Ext.data.NodeInterface} this This node
77616                  * @param {Ext.data.NodeInterface} node The child node to be appended
77617                  */
77618                 "beforeappend",
77619
77620                 /**
77621                  * @event beforeremove
77622                  * Fires before a child is removed, return false to cancel the remove.
77623                  * @param {Ext.data.NodeInterface} this This node
77624                  * @param {Ext.data.NodeInterface} node The child node to be removed
77625                  */
77626                 "beforeremove",
77627
77628                 /**
77629                  * @event beforemove
77630                  * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
77631                  * @param {Ext.data.NodeInterface} this This node
77632                  * @param {Ext.data.NodeInterface} oldParent The parent of this node
77633                  * @param {Ext.data.NodeInterface} newParent The new parent this node is moving to
77634                  * @param {Number} index The index it is being moved to
77635                  */
77636                 "beforemove",
77637
77638                  /**
77639                   * @event beforeinsert
77640                   * Fires before a new child is inserted, return false to cancel the insert.
77641                   * @param {Ext.data.NodeInterface} this This node
77642                   * @param {Ext.data.NodeInterface} node The child node to be inserted
77643                   * @param {Ext.data.NodeInterface} refNode The child node the node is being inserted before
77644                   */
77645                 "beforeinsert",
77646
77647                 /**
77648                  * @event expand
77649                  * Fires when this node is expanded.
77650                  * @param {Ext.data.NodeInterface} this The expanding node
77651                  */
77652                 "expand",
77653
77654                 /**
77655                  * @event collapse
77656                  * Fires when this node is collapsed.
77657                  * @param {Ext.data.NodeInterface} this The collapsing node
77658                  */
77659                 "collapse",
77660
77661                 /**
77662                  * @event beforeexpand
77663                  * Fires before this node is expanded.
77664                  * @param {Ext.data.NodeInterface} this The expanding node
77665                  */
77666                 "beforeexpand",
77667
77668                 /**
77669                  * @event beforecollapse
77670                  * Fires before this node is collapsed.
77671                  * @param {Ext.data.NodeInterface} this The collapsing node
77672                  */
77673                 "beforecollapse",
77674
77675                 /**
77676                  * @event sort
77677                  * Fires when this node's childNodes are sorted.
77678                  * @param {Ext.data.NodeInterface} this This node.
77679                  * @param {Ext.data.NodeInterface[]} childNodes The childNodes of this node.
77680                  */
77681                 "sort"
77682             ]);
77683
77684             return record;
77685         },
77686
77687         applyFields: function(modelClass, addFields) {
77688             var modelPrototype = modelClass.prototype,
77689                 fields = modelPrototype.fields,
77690                 keys = fields.keys,
77691                 ln = addFields.length,
77692                 addField, i, name,
77693                 newFields = [];
77694
77695             for (i = 0; i < ln; i++) {
77696                 addField = addFields[i];
77697                 if (!Ext.Array.contains(keys, addField.name)) {
77698                     addField = Ext.create('data.field', addField);
77699
77700                     newFields.push(addField);
77701                     fields.add(addField);
77702                 }
77703             }
77704
77705             return newFields;
77706         },
77707
77708         getPrototypeBody: function() {
77709             return {
77710                 isNode: true,
77711
77712                 /**
77713                  * Ensures that the passed object is an instance of a Record with the NodeInterface applied
77714                  * @return {Boolean}
77715                  */
77716                 createNode: function(node) {
77717                     if (Ext.isObject(node) && !node.isModel) {
77718                         node = Ext.ModelManager.create(node, this.modelName);
77719                     }
77720                     // Make sure the node implements the node interface
77721                     return Ext.data.NodeInterface.decorate(node);
77722                 },
77723
77724                 /**
77725                  * Returns true if this node is a leaf
77726                  * @return {Boolean}
77727                  */
77728                 isLeaf : function() {
77729                     return this.get('leaf') === true;
77730                 },
77731
77732                 /**
77733                  * Sets the first child of this node
77734                  * @private
77735                  * @param {Ext.data.NodeInterface} node
77736                  */
77737                 setFirstChild : function(node) {
77738                     this.firstChild = node;
77739                 },
77740
77741                 /**
77742                  * Sets the last child of this node
77743                  * @private
77744                  * @param {Ext.data.NodeInterface} node
77745                  */
77746                 setLastChild : function(node) {
77747                     this.lastChild = node;
77748                 },
77749
77750                 /**
77751                  * Updates general data of this node like isFirst, isLast, depth. This
77752                  * method is internally called after a node is moved. This shouldn't
77753                  * have to be called by the developer unless they are creating custom
77754                  * Tree plugins.
77755                  * @return {Boolean}
77756                  */
77757                 updateInfo: function(silent) {
77758                     var me = this,
77759                         isRoot = me.isRoot(),
77760                         parentNode = me.parentNode,
77761                         isFirst = (!parentNode ? true : parentNode.firstChild == me),
77762                         isLast = (!parentNode ? true : parentNode.lastChild == me),
77763                         depth = 0,
77764                         parent = me,
77765                         children = me.childNodes,
77766                         len = children.length,
77767                         i = 0;
77768
77769                     while (parent.parentNode) {
77770                         ++depth;
77771                         parent = parent.parentNode;
77772                     }
77773
77774                     me.beginEdit();
77775                     me.set({
77776                         isFirst: isFirst,
77777                         isLast: isLast,
77778                         depth: depth,
77779                         index: parentNode ? parentNode.indexOf(me) : 0,
77780                         parentId: parentNode ? parentNode.getId() : null
77781                     });
77782                     me.endEdit(silent);
77783                     if (silent) {
77784                         me.commit();
77785                     }
77786
77787                     for (i = 0; i < len; i++) {
77788                         children[i].updateInfo(silent);
77789                     }
77790                 },
77791
77792                 /**
77793                  * Returns true if this node is the last child of its parent
77794                  * @return {Boolean}
77795                  */
77796                 isLast : function() {
77797                    return this.get('isLast');
77798                 },
77799
77800                 /**
77801                  * Returns true if this node is the first child of its parent
77802                  * @return {Boolean}
77803                  */
77804                 isFirst : function() {
77805                    return this.get('isFirst');
77806                 },
77807
77808                 /**
77809                  * Returns true if this node has one or more child nodes, else false.
77810                  * @return {Boolean}
77811                  */
77812                 hasChildNodes : function() {
77813                     return !this.isLeaf() && this.childNodes.length > 0;
77814                 },
77815
77816                 /**
77817                  * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
77818                  * node attribute is explicitly specified as true, otherwise returns false.
77819                  * @return {Boolean}
77820                  */
77821                 isExpandable : function() {
77822                     var me = this;
77823
77824                     if (me.get('expandable')) {
77825                         return !(me.isLeaf() || (me.isLoaded() && !me.hasChildNodes()));
77826                     }
77827                     return false;
77828                 },
77829
77830                 /**
77831                  * Inserts node(s) as the last child node of this node.
77832                  *
77833                  * If the node was previously a child node of another parent node, it will be removed from that node first.
77834                  *
77835                  * @param {Ext.data.NodeInterface/Ext.data.NodeInterface[]} node The node or Array of nodes to append
77836                  * @return {Ext.data.NodeInterface} The appended node if single append, or null if an array was passed
77837                  */
77838                 appendChild : function(node, suppressEvents, suppressNodeUpdate) {
77839                     var me = this,
77840                         i, ln,
77841                         index,
77842                         oldParent,
77843                         ps;
77844
77845                     // if passed an array or multiple args do them one by one
77846                     if (Ext.isArray(node)) {
77847                         for (i = 0, ln = node.length; i < ln; i++) {
77848                             me.appendChild(node[i]);
77849                         }
77850                     } else {
77851                         // Make sure it is a record
77852                         node = me.createNode(node);
77853
77854                         if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
77855                             return false;
77856                         }
77857
77858                         index = me.childNodes.length;
77859                         oldParent = node.parentNode;
77860
77861                         // it's a move, make sure we move it cleanly
77862                         if (oldParent) {
77863                             if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index) === false) {
77864                                 return false;
77865                             }
77866                             oldParent.removeChild(node, null, false, true);
77867                         }
77868
77869                         index = me.childNodes.length;
77870                         if (index === 0) {
77871                             me.setFirstChild(node);
77872                         }
77873
77874                         me.childNodes.push(node);
77875                         node.parentNode = me;
77876                         node.nextSibling = null;
77877
77878                         me.setLastChild(node);
77879
77880                         ps = me.childNodes[index - 1];
77881                         if (ps) {
77882                             node.previousSibling = ps;
77883                             ps.nextSibling = node;
77884                             ps.updateInfo(suppressNodeUpdate);
77885                         } else {
77886                             node.previousSibling = null;
77887                         }
77888
77889                         node.updateInfo(suppressNodeUpdate);
77890
77891                         // As soon as we append a child to this node, we are loaded
77892                         if (!me.isLoaded()) {
77893                             me.set('loaded', true);
77894                         }
77895                         // If this node didnt have any childnodes before, update myself
77896                         else if (me.childNodes.length === 1) {
77897                             me.set('loaded', me.isLoaded());
77898                         }
77899
77900                         if (suppressEvents !== true) {
77901                             me.fireEvent("append", me, node, index);
77902
77903                             if (oldParent) {
77904                                 node.fireEvent("move", node, oldParent, me, index);
77905                             }
77906                         }
77907
77908                         return node;
77909                     }
77910                 },
77911
77912                 /**
77913                  * Returns the bubble target for this node
77914                  * @private
77915                  * @return {Object} The bubble target
77916                  */
77917                 getBubbleTarget: function() {
77918                     return this.parentNode;
77919                 },
77920
77921                 /**
77922                  * Removes a child node from this node.
77923                  * @param {Ext.data.NodeInterface} node The node to remove
77924                  * @param {Boolean} [destroy=false] True to destroy the node upon removal.
77925                  * @return {Ext.data.NodeInterface} The removed node
77926                  */
77927                 removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
77928                     var me = this,
77929                         index = me.indexOf(node);
77930
77931                     if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
77932                         return false;
77933                     }
77934
77935                     // remove it from childNodes collection
77936                     Ext.Array.erase(me.childNodes, index, 1);
77937
77938                     // update child refs
77939                     if (me.firstChild == node) {
77940                         me.setFirstChild(node.nextSibling);
77941                     }
77942                     if (me.lastChild == node) {
77943                         me.setLastChild(node.previousSibling);
77944                     }
77945
77946                     // update siblings
77947                     if (node.previousSibling) {
77948                         node.previousSibling.nextSibling = node.nextSibling;
77949                         node.previousSibling.updateInfo(suppressNodeUpdate);
77950                     }
77951                     if (node.nextSibling) {
77952                         node.nextSibling.previousSibling = node.previousSibling;
77953                         node.nextSibling.updateInfo(suppressNodeUpdate);
77954                     }
77955
77956                     if (suppressEvents !== true) {
77957                         me.fireEvent("remove", me, node);
77958                     }
77959
77960
77961                     // If this node suddenly doesnt have childnodes anymore, update myself
77962                     if (!me.childNodes.length) {
77963                         me.set('loaded', me.isLoaded());
77964                     }
77965
77966                     if (destroy) {
77967                         node.destroy(true);
77968                     } else {
77969                         node.clear();
77970                     }
77971
77972                     return node;
77973                 },
77974
77975                 /**
77976                  * Creates a copy (clone) of this Node.
77977                  * @param {String} [id] A new id, defaults to this Node's id.
77978                  * @param {Boolean} [deep=false] True to recursively copy all child Nodes into the new Node.
77979                  * False to copy without child Nodes.
77980                  * @return {Ext.data.NodeInterface} A copy of this Node.
77981                  */
77982                 copy: function(newId, deep) {
77983                     var me = this,
77984                         result = me.callOverridden(arguments),
77985                         len = me.childNodes ? me.childNodes.length : 0,
77986                         i;
77987
77988                     // Move child nodes across to the copy if required
77989                     if (deep) {
77990                         for (i = 0; i < len; i++) {
77991                             result.appendChild(me.childNodes[i].copy(true));
77992                         }
77993                     }
77994                     return result;
77995                 },
77996
77997                 /**
77998                  * Clears the node.
77999                  * @private
78000                  * @param {Boolean} [destroy=false] True to destroy the node.
78001                  */
78002                 clear : function(destroy) {
78003                     var me = this;
78004
78005                     // clear any references from the node
78006                     me.parentNode = me.previousSibling = me.nextSibling = null;
78007                     if (destroy) {
78008                         me.firstChild = me.lastChild = null;
78009                     }
78010                 },
78011
78012                 /**
78013                  * Destroys the node.
78014                  */
78015                 destroy : function(silent) {
78016                     /*
78017                      * Silent is to be used in a number of cases
78018                      * 1) When setRoot is called.
78019                      * 2) When destroy on the tree is called
78020                      * 3) For destroying child nodes on a node
78021                      */
78022                     var me = this,
78023                         options = me.destroyOptions;
78024
78025                     if (silent === true) {
78026                         me.clear(true);
78027                         Ext.each(me.childNodes, function(n) {
78028                             n.destroy(true);
78029                         });
78030                         me.childNodes = null;
78031                         delete me.destroyOptions;
78032                         me.callOverridden([options]);
78033                     } else {
78034                         me.destroyOptions = silent;
78035                         // overridden method will be called, since remove will end up calling destroy(true);
78036                         me.remove(true);
78037                     }
78038                 },
78039
78040                 /**
78041                  * Inserts the first node before the second node in this nodes childNodes collection.
78042                  * @param {Ext.data.NodeInterface} node The node to insert
78043                  * @param {Ext.data.NodeInterface} refNode The node to insert before (if null the node is appended)
78044                  * @return {Ext.data.NodeInterface} The inserted node
78045                  */
78046                 insertBefore : function(node, refNode, suppressEvents) {
78047                     var me = this,
78048                         index     = me.indexOf(refNode),
78049                         oldParent = node.parentNode,
78050                         refIndex  = index,
78051                         ps;
78052
78053                     if (!refNode) { // like standard Dom, refNode can be null for append
78054                         return me.appendChild(node);
78055                     }
78056
78057                     // nothing to do
78058                     if (node == refNode) {
78059                         return false;
78060                     }
78061
78062                     // Make sure it is a record with the NodeInterface
78063                     node = me.createNode(node);
78064
78065                     if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
78066                         return false;
78067                     }
78068
78069                     // when moving internally, indexes will change after remove
78070                     if (oldParent == me && me.indexOf(node) < index) {
78071                         refIndex--;
78072                     }
78073
78074                     // it's a move, make sure we move it cleanly
78075                     if (oldParent) {
78076                         if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index, refNode) === false) {
78077                             return false;
78078                         }
78079                         oldParent.removeChild(node);
78080                     }
78081
78082                     if (refIndex === 0) {
78083                         me.setFirstChild(node);
78084                     }
78085
78086                     Ext.Array.splice(me.childNodes, refIndex, 0, node);
78087                     node.parentNode = me;
78088
78089                     node.nextSibling = refNode;
78090                     refNode.previousSibling = node;
78091
78092                     ps = me.childNodes[refIndex - 1];
78093                     if (ps) {
78094                         node.previousSibling = ps;
78095                         ps.nextSibling = node;
78096                         ps.updateInfo();
78097                     } else {
78098                         node.previousSibling = null;
78099                     }
78100
78101                     node.updateInfo();
78102
78103                     if (!me.isLoaded()) {
78104                         me.set('loaded', true);
78105                     }
78106                     // If this node didnt have any childnodes before, update myself
78107                     else if (me.childNodes.length === 1) {
78108                         me.set('loaded', me.isLoaded());
78109                     }
78110
78111                     if (suppressEvents !== true) {
78112                         me.fireEvent("insert", me, node, refNode);
78113
78114                         if (oldParent) {
78115                             node.fireEvent("move", node, oldParent, me, refIndex, refNode);
78116                         }
78117                     }
78118
78119                     return node;
78120                 },
78121
78122                 /**
78123                  * Insert a node into this node
78124                  * @param {Number} index The zero-based index to insert the node at
78125                  * @param {Ext.data.Model} node The node to insert
78126                  * @return {Ext.data.Model} The record you just inserted
78127                  */
78128                 insertChild: function(index, node) {
78129                     var sibling = this.childNodes[index];
78130                     if (sibling) {
78131                         return this.insertBefore(node, sibling);
78132                     }
78133                     else {
78134                         return this.appendChild(node);
78135                     }
78136                 },
78137
78138                 /**
78139                  * Removes this node from its parent
78140                  * @param {Boolean} [destroy=false] True to destroy the node upon removal.
78141                  * @return {Ext.data.NodeInterface} this
78142                  */
78143                 remove : function(destroy, suppressEvents) {
78144                     var parentNode = this.parentNode;
78145
78146                     if (parentNode) {
78147                         parentNode.removeChild(this, destroy, suppressEvents, true);
78148                     }
78149                     return this;
78150                 },
78151
78152                 /**
78153                  * Removes all child nodes from this node.
78154                  * @param {Boolean} [destroy=false] <True to destroy the node upon removal.
78155                  * @return {Ext.data.NodeInterface} this
78156                  */
78157                 removeAll : function(destroy, suppressEvents) {
78158                     var cn = this.childNodes,
78159                         n;
78160
78161                     while ((n = cn[0])) {
78162                         this.removeChild(n, destroy, suppressEvents);
78163                     }
78164                     return this;
78165                 },
78166
78167                 /**
78168                  * Returns the child node at the specified index.
78169                  * @param {Number} index
78170                  * @return {Ext.data.NodeInterface}
78171                  */
78172                 getChildAt : function(index) {
78173                     return this.childNodes[index];
78174                 },
78175
78176                 /**
78177                  * Replaces one child node in this node with another.
78178                  * @param {Ext.data.NodeInterface} newChild The replacement node
78179                  * @param {Ext.data.NodeInterface} oldChild The node to replace
78180                  * @return {Ext.data.NodeInterface} The replaced node
78181                  */
78182                 replaceChild : function(newChild, oldChild, suppressEvents) {
78183                     var s = oldChild ? oldChild.nextSibling : null;
78184
78185                     this.removeChild(oldChild, suppressEvents);
78186                     this.insertBefore(newChild, s, suppressEvents);
78187                     return oldChild;
78188                 },
78189
78190                 /**
78191                  * Returns the index of a child node
78192                  * @param {Ext.data.NodeInterface} node
78193                  * @return {Number} The index of the node or -1 if it was not found
78194                  */
78195                 indexOf : function(child) {
78196                     return Ext.Array.indexOf(this.childNodes, child);
78197                 },
78198
78199                 /**
78200                  * Gets the hierarchical path from the root of the current node.
78201                  * @param {String} [field] The field to construct the path from. Defaults to the model idProperty.
78202                  * @param {String} [separator="/"] A separator to use.
78203                  * @return {String} The node path
78204                  */
78205                 getPath: function(field, separator) {
78206                     field = field || this.idProperty;
78207                     separator = separator || '/';
78208
78209                     var path = [this.get(field)],
78210                         parent = this.parentNode;
78211
78212                     while (parent) {
78213                         path.unshift(parent.get(field));
78214                         parent = parent.parentNode;
78215                     }
78216                     return separator + path.join(separator);
78217                 },
78218
78219                 /**
78220                  * Returns depth of this node (the root node has a depth of 0)
78221                  * @return {Number}
78222                  */
78223                 getDepth : function() {
78224                     return this.get('depth');
78225                 },
78226
78227                 /**
78228                  * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
78229                  * will be the args provided or the current node. If the function returns false at any point,
78230                  * the bubble is stopped.
78231                  * @param {Function} fn The function to call
78232                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
78233                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78234                  */
78235                 bubble : function(fn, scope, args) {
78236                     var p = this;
78237                     while (p) {
78238                         if (fn.apply(scope || p, args || [p]) === false) {
78239                             break;
78240                         }
78241                         p = p.parentNode;
78242                     }
78243                 },
78244
78245                 cascade: function() {
78246                     if (Ext.isDefined(Ext.global.console)) {
78247                         Ext.global.console.warn('Ext.data.Node: cascade has been deprecated. Please use cascadeBy instead.');
78248                     }
78249                     return this.cascadeBy.apply(this, arguments);
78250                 },
78251
78252                 /**
78253                  * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
78254                  * will be the args provided or the current node. If the function returns false at any point,
78255                  * the cascade is stopped on that branch.
78256                  * @param {Function} fn The function to call
78257                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
78258                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78259                  */
78260                 cascadeBy : function(fn, scope, args) {
78261                     if (fn.apply(scope || this, args || [this]) !== false) {
78262                         var childNodes = this.childNodes,
78263                             length     = childNodes.length,
78264                             i;
78265
78266                         for (i = 0; i < length; i++) {
78267                             childNodes[i].cascadeBy(fn, scope, args);
78268                         }
78269                     }
78270                 },
78271
78272                 /**
78273                  * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
78274                  * will be the args provided or the current node. If the function returns false at any point,
78275                  * the iteration stops.
78276                  * @param {Function} fn The function to call
78277                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node in iteration.
78278                  * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
78279                  */
78280                 eachChild : function(fn, scope, args) {
78281                     var childNodes = this.childNodes,
78282                         length     = childNodes.length,
78283                         i;
78284
78285                     for (i = 0; i < length; i++) {
78286                         if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
78287                             break;
78288                         }
78289                     }
78290                 },
78291
78292                 /**
78293                  * Finds the first child that has the attribute with the specified value.
78294                  * @param {String} attribute The attribute name
78295                  * @param {Object} value The value to search for
78296                  * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
78297                  * @return {Ext.data.NodeInterface} The found child or null if none was found
78298                  */
78299                 findChild : function(attribute, value, deep) {
78300                     return this.findChildBy(function() {
78301                         return this.get(attribute) == value;
78302                     }, null, deep);
78303                 },
78304
78305                 /**
78306                  * Finds the first child by a custom function. The child matches if the function passed returns true.
78307                  * @param {Function} fn A function which must return true if the passed Node is the required Node.
78308                  * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the Node being tested.
78309                  * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
78310                  * @return {Ext.data.NodeInterface} The found child or null if none was found
78311                  */
78312                 findChildBy : function(fn, scope, deep) {
78313                     var cs = this.childNodes,
78314                         len = cs.length,
78315                         i = 0, n, res;
78316
78317                     for (; i < len; i++) {
78318                         n = cs[i];
78319                         if (fn.call(scope || n, n) === true) {
78320                             return n;
78321                         }
78322                         else if (deep) {
78323                             res = n.findChildBy(fn, scope, deep);
78324                             if (res !== null) {
78325                                 return res;
78326                             }
78327                         }
78328                     }
78329
78330                     return null;
78331                 },
78332
78333                 /**
78334                  * Returns true if this node is an ancestor (at any point) of the passed node.
78335                  * @param {Ext.data.NodeInterface} node
78336                  * @return {Boolean}
78337                  */
78338                 contains : function(node) {
78339                     return node.isAncestor(this);
78340                 },
78341
78342                 /**
78343                  * Returns true if the passed node is an ancestor (at any point) of this node.
78344                  * @param {Ext.data.NodeInterface} node
78345                  * @return {Boolean}
78346                  */
78347                 isAncestor : function(node) {
78348                     var p = this.parentNode;
78349                     while (p) {
78350                         if (p == node) {
78351                             return true;
78352                         }
78353                         p = p.parentNode;
78354                     }
78355                     return false;
78356                 },
78357
78358                 /**
78359                  * Sorts this nodes children using the supplied sort function.
78360                  * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
78361                  * @param {Boolean} [recursive=false] True to apply this sort recursively
78362                  * @param {Boolean} [suppressEvent=false] True to not fire a sort event.
78363                  */
78364                 sort : function(sortFn, recursive, suppressEvent) {
78365                     var cs  = this.childNodes,
78366                         ln = cs.length,
78367                         i, n;
78368
78369                     if (ln > 0) {
78370                         Ext.Array.sort(cs, sortFn);
78371                         for (i = 0; i < ln; i++) {
78372                             n = cs[i];
78373                             n.previousSibling = cs[i-1];
78374                             n.nextSibling = cs[i+1];
78375
78376                             if (i === 0) {
78377                                 this.setFirstChild(n);
78378                                 n.updateInfo();
78379                             }
78380                             if (i == ln - 1) {
78381                                 this.setLastChild(n);
78382                                 n.updateInfo();
78383                             }
78384                             if (recursive && !n.isLeaf()) {
78385                                 n.sort(sortFn, true, true);
78386                             }
78387                         }
78388
78389                         if (suppressEvent !== true) {
78390                             this.fireEvent('sort', this, cs);
78391                         }
78392                     }
78393                 },
78394
78395                 /**
78396                  * Returns true if this node is expaned
78397                  * @return {Boolean}
78398                  */
78399                 isExpanded: function() {
78400                     return this.get('expanded');
78401                 },
78402
78403                 /**
78404                  * Returns true if this node is loaded
78405                  * @return {Boolean}
78406                  */
78407                 isLoaded: function() {
78408                     return this.get('loaded');
78409                 },
78410
78411                 /**
78412                  * Returns true if this node is loading
78413                  * @return {Boolean}
78414                  */
78415                 isLoading: function() {
78416                     return this.get('loading');
78417                 },
78418
78419                 /**
78420                  * Returns true if this node is the root node
78421                  * @return {Boolean}
78422                  */
78423                 isRoot: function() {
78424                     return !this.parentNode;
78425                 },
78426
78427                 /**
78428                  * Returns true if this node is visible
78429                  * @return {Boolean}
78430                  */
78431                 isVisible: function() {
78432                     var parent = this.parentNode;
78433                     while (parent) {
78434                         if (!parent.isExpanded()) {
78435                             return false;
78436                         }
78437                         parent = parent.parentNode;
78438                     }
78439                     return true;
78440                 },
78441
78442                 /**
78443                  * Expand this node.
78444                  * @param {Boolean} [recursive=false] True to recursively expand all the children
78445                  * @param {Function} [callback] The function to execute once the expand completes
78446                  * @param {Object} [scope] The scope to run the callback in
78447                  */
78448                 expand: function(recursive, callback, scope) {
78449                     var me = this;
78450
78451                     // all paths must call the callback (eventually) or things like
78452                     // selectPath fail
78453
78454                     // First we start by checking if this node is a parent
78455                     if (!me.isLeaf()) {
78456                         // If it's loaded, wait until it loads before proceeding
78457                         if (me.isLoading()) {
78458                             me.on('expand', function(){
78459                                 me.expand(recursive, callback, scope);
78460                             }, me, {single: true});
78461                         } else {
78462                             // Now we check if this record is already expanding or expanded
78463                             if (!me.isExpanded()) {
78464                                 // The TreeStore actually listens for the beforeexpand method and checks
78465                                 // whether we have to asynchronously load the children from the server
78466                                 // first. Thats why we pass a callback function to the event that the
78467                                 // store can call once it has loaded and parsed all the children.
78468                                 me.fireEvent('beforeexpand', me, function(){
78469                                     me.set('expanded', true);
78470                                     me.fireEvent('expand', me, me.childNodes, false);
78471
78472                                     // Call the expandChildren method if recursive was set to true
78473                                     if (recursive) {
78474                                         me.expandChildren(true, callback, scope);
78475                                     } else {
78476                                         Ext.callback(callback, scope || me, [me.childNodes]);
78477                                     }
78478                                 }, me);
78479                             } else if (recursive) {
78480                                 // If it is is already expanded but we want to recursively expand then call expandChildren
78481                                 me.expandChildren(true, callback, scope);
78482                             } else {
78483                                 Ext.callback(callback, scope || me, [me.childNodes]);
78484                             }
78485                         }
78486                     } else {
78487                         // If it's not then we fire the callback right away
78488                         Ext.callback(callback, scope || me); // leaf = no childNodes
78489                     }
78490                 },
78491
78492                 /**
78493                  * Expand all the children of this node.
78494                  * @param {Boolean} [recursive=false] True to recursively expand all the children
78495                  * @param {Function} [callback] The function to execute once all the children are expanded
78496                  * @param {Object} [scope] The scope to run the callback in
78497                  */
78498                 expandChildren: function(recursive, callback, scope) {
78499                     var me = this,
78500                         i = 0,
78501                         nodes = me.childNodes,
78502                         ln = nodes.length,
78503                         node,
78504                         expanding = 0;
78505
78506                     for (; i < ln; ++i) {
78507                         node = nodes[i];
78508                         if (!node.isLeaf() && !node.isExpanded()) {
78509                             expanding++;
78510                             nodes[i].expand(recursive, function () {
78511                                 expanding--;
78512                                 if (callback && !expanding) {
78513                                     Ext.callback(callback, scope || me, [me.childNodes]);
78514                                 }
78515                             });
78516                         }
78517                     }
78518
78519                     if (!expanding && callback) {
78520                         Ext.callback(callback, scope || me, [me.childNodes]);                    }
78521                 },
78522
78523                 /**
78524                  * Collapse this node.
78525                  * @param {Boolean} [recursive=false] True to recursively collapse all the children
78526                  * @param {Function} [callback] The function to execute once the collapse completes
78527                  * @param {Object} [scope] The scope to run the callback in
78528                  */
78529                 collapse: function(recursive, callback, scope) {
78530                     var me = this;
78531
78532                     // First we start by checking if this node is a parent
78533                     if (!me.isLeaf()) {
78534                         // Now we check if this record is already collapsing or collapsed
78535                         if (!me.collapsing && me.isExpanded()) {
78536                             me.fireEvent('beforecollapse', me, function() {
78537                                 me.set('expanded', false);
78538                                 me.fireEvent('collapse', me, me.childNodes, false);
78539
78540                                 // Call the collapseChildren method if recursive was set to true
78541                                 if (recursive) {
78542                                     me.collapseChildren(true, callback, scope);
78543                                 }
78544                                 else {
78545                                     Ext.callback(callback, scope || me, [me.childNodes]);
78546                                 }
78547                             }, me);
78548                         }
78549                         // If it is is already collapsed but we want to recursively collapse then call collapseChildren
78550                         else if (recursive) {
78551                             me.collapseChildren(true, callback, scope);
78552                         }
78553                     }
78554                     // If it's not then we fire the callback right away
78555                     else {
78556                         Ext.callback(callback, scope || me, [me.childNodes]);
78557                     }
78558                 },
78559
78560                 /**
78561                  * Collapse all the children of this node.
78562                  * @param {Function} [recursive=false] True to recursively collapse all the children
78563                  * @param {Function} [callback] The function to execute once all the children are collapsed
78564                  * @param {Object} [scope] The scope to run the callback in
78565                  */
78566                 collapseChildren: function(recursive, callback, scope) {
78567                     var me = this,
78568                         i = 0,
78569                         nodes = me.childNodes,
78570                         ln = nodes.length,
78571                         node,
78572                         collapsing = 0;
78573
78574                     for (; i < ln; ++i) {
78575                         node = nodes[i];
78576                         if (!node.isLeaf() && node.isExpanded()) {
78577                             collapsing++;
78578                             nodes[i].collapse(recursive, function () {
78579                                 collapsing--;
78580                                 if (callback && !collapsing) {
78581                                     Ext.callback(callback, scope || me, [me.childNodes]);
78582                                 }
78583                             });
78584                         }
78585                     }
78586
78587                     if (!collapsing && callback) {
78588                         Ext.callback(callback, scope || me, [me.childNodes]);
78589                     }
78590                 }
78591             };
78592         }
78593     }
78594 });
78595 /**
78596  * @class Ext.data.NodeStore
78597  * @extends Ext.data.AbstractStore
78598  * Node Store
78599  * @ignore
78600  */
78601 Ext.define('Ext.data.NodeStore', {
78602     extend: 'Ext.data.Store',
78603     alias: 'store.node',
78604     requires: ['Ext.data.NodeInterface'],
78605     
78606     /**
78607      * @cfg {Ext.data.Model} node
78608      * The Record you want to bind this Store to. Note that
78609      * this record will be decorated with the Ext.data.NodeInterface if this is not the
78610      * case yet.
78611      */
78612     node: null,
78613     
78614     /**
78615      * @cfg {Boolean} recursive
78616      * Set this to true if you want this NodeStore to represent
78617      * all the descendents of the node in its flat data collection. This is useful for
78618      * rendering a tree structure to a DataView and is being used internally by
78619      * the TreeView. Any records that are moved, removed, inserted or appended to the
78620      * node at any depth below the node this store is bound to will be automatically
78621      * updated in this Store's internal flat data structure.
78622      */
78623     recursive: false,
78624     
78625     /** 
78626      * @cfg {Boolean} rootVisible
78627      * False to not include the root node in this Stores collection.
78628      */    
78629     rootVisible: false,
78630     
78631     constructor: function(config) {
78632         var me = this,
78633             node;
78634             
78635         config = config || {};
78636         Ext.apply(me, config);
78637         
78638
78639         config.proxy = {type: 'proxy'};
78640         me.callParent([config]);
78641
78642         me.addEvents('expand', 'collapse', 'beforeexpand', 'beforecollapse');
78643         
78644         node = me.node;
78645         if (node) {
78646             me.node = null;
78647             me.setNode(node);
78648         }
78649     },
78650     
78651     setNode: function(node) {
78652         var me = this;
78653         
78654         if (me.node && me.node != node) {
78655             // We want to unbind our listeners on the old node
78656             me.mun(me.node, {
78657                 expand: me.onNodeExpand,
78658                 collapse: me.onNodeCollapse,
78659                 append: me.onNodeAppend,
78660                 insert: me.onNodeInsert,
78661                 remove: me.onNodeRemove,
78662                 sort: me.onNodeSort,
78663                 scope: me
78664             });
78665             me.node = null;
78666         }
78667         
78668         if (node) {
78669             Ext.data.NodeInterface.decorate(node);
78670             me.removeAll();
78671             if (me.rootVisible) {
78672                 me.add(node);
78673             }
78674             me.mon(node, {
78675                 expand: me.onNodeExpand,
78676                 collapse: me.onNodeCollapse,
78677                 append: me.onNodeAppend,
78678                 insert: me.onNodeInsert,
78679                 remove: me.onNodeRemove,
78680                 sort: me.onNodeSort,
78681                 scope: me
78682             });
78683             me.node = node;
78684             if (node.isExpanded() && node.isLoaded()) {
78685                 me.onNodeExpand(node, node.childNodes, true);
78686             }
78687         }
78688     },
78689     
78690     onNodeSort: function(node, childNodes) {
78691         var me = this;
78692         
78693         if ((me.indexOf(node) !== -1 || (node === me.node && !me.rootVisible) && node.isExpanded())) {
78694             me.onNodeCollapse(node, childNodes, true);
78695             me.onNodeExpand(node, childNodes, true);
78696         }
78697     },
78698     
78699     onNodeExpand: function(parent, records, suppressEvent) {
78700         var me = this,
78701             insertIndex = me.indexOf(parent) + 1,
78702             ln = records ? records.length : 0,
78703             i, record;
78704             
78705         if (!me.recursive && parent !== me.node) {
78706             return;
78707         }
78708         
78709         if (!me.isVisible(parent)) {
78710             return;
78711         }
78712
78713         if (!suppressEvent && me.fireEvent('beforeexpand', parent, records, insertIndex) === false) {
78714             return;
78715         }
78716         
78717         if (ln) {
78718             me.insert(insertIndex, records);
78719             for (i = 0; i < ln; i++) {
78720                 record = records[i];
78721                 if (record.isExpanded()) {
78722                     if (record.isLoaded()) {
78723                         // Take a shortcut                        
78724                         me.onNodeExpand(record, record.childNodes, true);
78725                     }
78726                     else {
78727                         record.set('expanded', false);
78728                         record.expand();
78729                     }
78730                 }
78731             }
78732         }
78733
78734         if (!suppressEvent) {
78735             me.fireEvent('expand', parent, records);
78736         }
78737     },
78738
78739     onNodeCollapse: function(parent, records, suppressEvent) {
78740         var me = this,
78741             ln = records.length,
78742             collapseIndex = me.indexOf(parent) + 1,
78743             i, record;
78744             
78745         if (!me.recursive && parent !== me.node) {
78746             return;
78747         }
78748         
78749         if (!suppressEvent && me.fireEvent('beforecollapse', parent, records, collapseIndex) === false) {
78750             return;
78751         }
78752
78753         for (i = 0; i < ln; i++) {
78754             record = records[i];
78755             me.remove(record);
78756             if (record.isExpanded()) {
78757                 me.onNodeCollapse(record, record.childNodes, true);
78758             }
78759         }
78760         
78761         if (!suppressEvent) {
78762             me.fireEvent('collapse', parent, records, collapseIndex);
78763         }
78764     },
78765     
78766     onNodeAppend: function(parent, node, index) {
78767         var me = this,
78768             refNode, sibling;
78769
78770         if (me.isVisible(node)) {
78771             if (index === 0) {
78772                 refNode = parent;
78773             } else {
78774                 sibling = node.previousSibling;
78775                 while (sibling.isExpanded() && sibling.lastChild) {
78776                     sibling = sibling.lastChild;
78777                 }
78778                 refNode = sibling;
78779             }
78780             me.insert(me.indexOf(refNode) + 1, node);
78781             if (!node.isLeaf() && node.isExpanded()) {
78782                 if (node.isLoaded()) {
78783                     // Take a shortcut                        
78784                     me.onNodeExpand(node, node.childNodes, true);
78785                 }
78786                 else {
78787                     node.set('expanded', false);
78788                     node.expand();
78789                 }
78790             }
78791         } 
78792     },
78793     
78794     onNodeInsert: function(parent, node, refNode) {
78795         var me = this,
78796             index = this.indexOf(refNode);
78797             
78798         if (index != -1 && me.isVisible(node)) {
78799             me.insert(index, node);
78800             if (!node.isLeaf() && node.isExpanded()) {
78801                 if (node.isLoaded()) {
78802                     // Take a shortcut                        
78803                     me.onNodeExpand(node, node.childNodes, true);
78804                 }
78805                 else {
78806                     node.set('expanded', false);
78807                     node.expand();
78808                 }
78809             }
78810         }
78811     },
78812     
78813     onNodeRemove: function(parent, node, index) {
78814         var me = this;
78815         if (me.indexOf(node) != -1) {
78816             if (!node.isLeaf() && node.isExpanded()) {
78817                 me.onNodeCollapse(node, node.childNodes, true);
78818             }            
78819             me.remove(node);
78820         }
78821     },
78822     
78823     isVisible: function(node) {
78824         var parent = node.parentNode;
78825         while (parent) {
78826             if (parent === this.node && !this.rootVisible && parent.isExpanded()) {
78827                 return true;
78828             }
78829             
78830             if (this.indexOf(parent) === -1 || !parent.isExpanded()) {
78831                 return false;
78832             }
78833             
78834             parent = parent.parentNode;
78835         }
78836         return true;
78837     }
78838 });
78839 /**
78840  * @author Ed Spencer
78841  * 
78842  * Simple class that represents a Request that will be made by any {@link Ext.data.proxy.Server} subclass.
78843  * All this class does is standardize the representation of a Request as used by any ServerProxy subclass,
78844  * it does not contain any actual logic or perform the request itself.
78845  */
78846 Ext.define('Ext.data.Request', {
78847     /**
78848      * @cfg {String} action
78849      * The name of the action this Request represents. Usually one of 'create', 'read', 'update' or 'destroy'.
78850      */
78851     action: undefined,
78852     
78853     /**
78854      * @cfg {Object} params
78855      * HTTP request params. The Proxy and its Writer have access to and can modify this object.
78856      */
78857     params: undefined,
78858     
78859     /**
78860      * @cfg {String} method
78861      * The HTTP method to use on this Request. Should be one of 'GET', 'POST', 'PUT' or 'DELETE'.
78862      */
78863     method: 'GET',
78864     
78865     /**
78866      * @cfg {String} url
78867      * The url to access on this Request
78868      */
78869     url: undefined,
78870
78871     /**
78872      * Creates the Request object.
78873      * @param {Object} [config] Config object.
78874      */
78875     constructor: function(config) {
78876         Ext.apply(this, config);
78877     }
78878 });
78879 /**
78880  * @author Don Griffin
78881  *
78882  * This class is a sequential id generator. A simple use of this class would be like so:
78883  *
78884  *     Ext.define('MyApp.data.MyModel', {
78885  *         extend: 'Ext.data.Model',
78886  *         idgen: 'sequential'
78887  *     });
78888  *     // assign id's of 1, 2, 3, etc.
78889  *
78890  * An example of a configured generator would be:
78891  *
78892  *     Ext.define('MyApp.data.MyModel', {
78893  *         extend: 'Ext.data.Model',
78894  *         idgen: {
78895  *             type: 'sequential',
78896  *             prefix: 'ID_',
78897  *             seed: 1000
78898  *         }
78899  *     });
78900  *     // assign id's of ID_1000, ID_1001, ID_1002, etc.
78901  *
78902  */
78903 Ext.define('Ext.data.SequentialIdGenerator', {
78904     extend: 'Ext.data.IdGenerator',
78905     alias: 'idgen.sequential',
78906
78907     constructor: function() {
78908         var me = this;
78909
78910         me.callParent(arguments);
78911
78912         me.parts = [ me.prefix, ''];
78913     },
78914
78915     /**
78916      * @cfg {String} prefix
78917      * The string to place in front of the sequential number for each generated id. The
78918      * default is blank.
78919      */
78920     prefix: '',
78921
78922     /**
78923      * @cfg {Number} seed
78924      * The number at which to start generating sequential id's. The default is 1.
78925      */
78926     seed: 1,
78927
78928     /**
78929      * Generates and returns the next id.
78930      * @return {String} The next id.
78931      */
78932     generate: function () {
78933         var me = this,
78934             parts = me.parts;
78935
78936         parts[1] = me.seed++;
78937         return parts.join('');
78938     }
78939 });
78940
78941 /**
78942  * @class Ext.data.Tree
78943  *
78944  * This class is used as a container for a series of nodes. The nodes themselves maintain
78945  * the relationship between parent/child. The tree itself acts as a manager. It gives functionality
78946  * to retrieve a node by its identifier: {@link #getNodeById}.
78947  *
78948  * The tree also relays events from any of it's child nodes, allowing them to be handled in a
78949  * centralized fashion. In general this class is not used directly, rather used internally
78950  * by other parts of the framework.
78951  *
78952  */
78953 Ext.define('Ext.data.Tree', {
78954     alias: 'data.tree',
78955
78956     mixins: {
78957         observable: "Ext.util.Observable"
78958     },
78959
78960     /**
78961      * @property {Ext.data.NodeInterface}
78962      * The root node for this tree
78963      */
78964     root: null,
78965
78966     /**
78967      * Creates new Tree object.
78968      * @param {Ext.data.NodeInterface} root (optional) The root node
78969      */
78970     constructor: function(root) {
78971         var me = this;
78972
78973         
78974
78975         me.mixins.observable.constructor.call(me);
78976
78977         if (root) {
78978             me.setRootNode(root);
78979         }
78980     },
78981
78982     /**
78983      * Returns the root node for this tree.
78984      * @return {Ext.data.NodeInterface}
78985      */
78986     getRootNode : function() {
78987         return this.root;
78988     },
78989
78990     /**
78991      * Sets the root node for this tree.
78992      * @param {Ext.data.NodeInterface} node
78993      * @return {Ext.data.NodeInterface} The root node
78994      */
78995     setRootNode : function(node) {
78996         var me = this;
78997
78998         me.root = node;
78999         Ext.data.NodeInterface.decorate(node);
79000
79001         if (me.fireEvent('beforeappend', null, node) !== false) {
79002             node.set('root', true);
79003             node.updateInfo();
79004
79005             me.relayEvents(node, [
79006                 /**
79007                  * @event append
79008                  * @alias Ext.data.NodeInterface#append
79009                  */
79010                 "append",
79011
79012                 /**
79013                  * @event remove
79014                  * @alias Ext.data.NodeInterface#remove
79015                  */
79016                 "remove",
79017
79018                 /**
79019                  * @event move
79020                  * @alias Ext.data.NodeInterface#move
79021                  */
79022                 "move",
79023
79024                 /**
79025                  * @event insert
79026                  * @alias Ext.data.NodeInterface#insert
79027                  */
79028                 "insert",
79029
79030                 /**
79031                  * @event beforeappend
79032                  * @alias Ext.data.NodeInterface#beforeappend
79033                  */
79034                 "beforeappend",
79035
79036                 /**
79037                  * @event beforeremove
79038                  * @alias Ext.data.NodeInterface#beforeremove
79039                  */
79040                 "beforeremove",
79041
79042                 /**
79043                  * @event beforemove
79044                  * @alias Ext.data.NodeInterface#beforemove
79045                  */
79046                 "beforemove",
79047
79048                 /**
79049                  * @event beforeinsert
79050                  * @alias Ext.data.NodeInterface#beforeinsert
79051                  */
79052                 "beforeinsert",
79053
79054                  /**
79055                   * @event expand
79056                   * @alias Ext.data.NodeInterface#expand
79057                   */
79058                  "expand",
79059
79060                  /**
79061                   * @event collapse
79062                   * @alias Ext.data.NodeInterface#collapse
79063                   */
79064                  "collapse",
79065
79066                  /**
79067                   * @event beforeexpand
79068                   * @alias Ext.data.NodeInterface#beforeexpand
79069                   */
79070                  "beforeexpand",
79071
79072                  /**
79073                   * @event beforecollapse
79074                   * @alias Ext.data.NodeInterface#beforecollapse
79075                   */
79076                  "beforecollapse" ,
79077
79078                  /**
79079                   * @event rootchange
79080                   * Fires whenever the root node is changed in the tree.
79081                   * @param {Ext.data.Model} root The new root
79082                   */
79083                  "rootchange"
79084             ]);
79085
79086             node.on({
79087                 scope: me,
79088                 insert: me.onNodeInsert,
79089                 append: me.onNodeAppend,
79090                 remove: me.onNodeRemove
79091             });
79092
79093             me.nodeHash = {};
79094             me.registerNode(node);
79095             me.fireEvent('append', null, node);
79096             me.fireEvent('rootchange', node);
79097         }
79098
79099         return node;
79100     },
79101
79102     /**
79103      * Flattens all the nodes in the tree into an array.
79104      * @private
79105      * @return {Ext.data.NodeInterface[]} The flattened nodes.
79106      */
79107     flatten: function(){
79108         var nodes = [],
79109             hash = this.nodeHash,
79110             key;
79111
79112         for (key in hash) {
79113             if (hash.hasOwnProperty(key)) {
79114                 nodes.push(hash[key]);
79115             }
79116         }
79117         return nodes;
79118     },
79119
79120     /**
79121      * Fired when a node is inserted into the root or one of it's children
79122      * @private
79123      * @param {Ext.data.NodeInterface} parent The parent node
79124      * @param {Ext.data.NodeInterface} node The inserted node
79125      */
79126     onNodeInsert: function(parent, node) {
79127         this.registerNode(node, true);
79128     },
79129
79130     /**
79131      * Fired when a node is appended into the root or one of it's children
79132      * @private
79133      * @param {Ext.data.NodeInterface} parent The parent node
79134      * @param {Ext.data.NodeInterface} node The appended node
79135      */
79136     onNodeAppend: function(parent, node) {
79137         this.registerNode(node, true);
79138     },
79139
79140     /**
79141      * Fired when a node is removed from the root or one of it's children
79142      * @private
79143      * @param {Ext.data.NodeInterface} parent The parent node
79144      * @param {Ext.data.NodeInterface} node The removed node
79145      */
79146     onNodeRemove: function(parent, node) {
79147         this.unregisterNode(node, true);
79148     },
79149
79150     /**
79151      * Gets a node in this tree by its id.
79152      * @param {String} id
79153      * @return {Ext.data.NodeInterface} The match node.
79154      */
79155     getNodeById : function(id) {
79156         return this.nodeHash[id];
79157     },
79158
79159     /**
79160      * Registers a node with the tree
79161      * @private
79162      * @param {Ext.data.NodeInterface} The node to register
79163      * @param {Boolean} [includeChildren] True to unregister any child nodes
79164      */
79165     registerNode : function(node, includeChildren) {
79166         this.nodeHash[node.getId() || node.internalId] = node;
79167         if (includeChildren === true) {
79168             node.eachChild(function(child){
79169                 this.registerNode(child, true);
79170             }, this);
79171         }
79172     },
79173
79174     /**
79175      * Unregisters a node with the tree
79176      * @private
79177      * @param {Ext.data.NodeInterface} The node to unregister
79178      * @param {Boolean} [includeChildren] True to unregister any child nodes
79179      */
79180     unregisterNode : function(node, includeChildren) {
79181         delete this.nodeHash[node.getId() || node.internalId];
79182         if (includeChildren === true) {
79183             node.eachChild(function(child){
79184                 this.unregisterNode(child, true);
79185             }, this);
79186         }
79187     },
79188
79189     /**
79190      * Sorts this tree
79191      * @private
79192      * @param {Function} sorterFn The function to use for sorting
79193      * @param {Boolean} recursive True to perform recursive sorting
79194      */
79195     sort: function(sorterFn, recursive) {
79196         this.getRootNode().sort(sorterFn, recursive);
79197     },
79198
79199      /**
79200      * Filters this tree
79201      * @private
79202      * @param {Function} sorterFn The function to use for filtering
79203      * @param {Boolean} recursive True to perform recursive filtering
79204      */
79205     filter: function(filters, recursive) {
79206         this.getRootNode().filter(filters, recursive);
79207     }
79208 });
79209 /**
79210  * The TreeStore is a store implementation that is backed by by an {@link Ext.data.Tree}.
79211  * It provides convenience methods for loading nodes, as well as the ability to use
79212  * the hierarchical tree structure combined with a store. This class is generally used
79213  * in conjunction with {@link Ext.tree.Panel}. This class also relays many events from
79214  * the Tree for convenience.
79215  *
79216  * # Using Models
79217  *
79218  * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
79219  * The standard Tree fields will also be copied onto the Model for maintaining their state. These fields are listed
79220  * in the {@link Ext.data.NodeInterface} documentation.
79221  *
79222  * # Reading Nested Data
79223  *
79224  * For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property,
79225  * so the reader can find nested data for each node. If a root is not specified, it will default to
79226  * 'children'.
79227  */
79228 Ext.define('Ext.data.TreeStore', {
79229     extend: 'Ext.data.AbstractStore',
79230     alias: 'store.tree',
79231     requires: ['Ext.data.Tree', 'Ext.data.NodeInterface', 'Ext.data.NodeStore'],
79232
79233     /**
79234      * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
79235      * The root node for this store. For example:
79236      *
79237      *     root: {
79238      *         expanded: true,
79239      *         text: "My Root",
79240      *         children: [
79241      *             { text: "Child 1", leaf: true },
79242      *             { text: "Child 2", expanded: true, children: [
79243      *                 { text: "GrandChild", leaf: true }
79244      *             ] }
79245      *         ]
79246      *     }
79247      *
79248      * Setting the `root` config option is the same as calling {@link #setRootNode}.
79249      */
79250
79251     /**
79252      * @cfg {Boolean} clearOnLoad
79253      * Remove previously existing child nodes before loading. Default to true.
79254      */
79255     clearOnLoad : true,
79256
79257     /**
79258      * @cfg {String} nodeParam
79259      * The name of the parameter sent to the server which contains the identifier of the node.
79260      * Defaults to 'node'.
79261      */
79262     nodeParam: 'node',
79263
79264     /**
79265      * @cfg {String} defaultRootId
79266      * The default root id. Defaults to 'root'
79267      */
79268     defaultRootId: 'root',
79269
79270     /**
79271      * @cfg {String} defaultRootProperty
79272      * The root property to specify on the reader if one is not explicitly defined.
79273      */
79274     defaultRootProperty: 'children',
79275
79276     /**
79277      * @cfg {Boolean} folderSort
79278      * Set to true to automatically prepend a leaf sorter. Defaults to `undefined`.
79279      */
79280     folderSort: false,
79281
79282     constructor: function(config) {
79283         var me = this,
79284             root,
79285             fields;
79286
79287         config = Ext.apply({}, config);
79288
79289         /**
79290          * If we have no fields declare for the store, add some defaults.
79291          * These will be ignored if a model is explicitly specified.
79292          */
79293         fields = config.fields || me.fields;
79294         if (!fields) {
79295             config.fields = [{name: 'text', type: 'string'}];
79296         }
79297
79298         me.callParent([config]);
79299
79300         // We create our data tree.
79301         me.tree = Ext.create('Ext.data.Tree');
79302
79303         me.relayEvents(me.tree, [
79304             /**
79305              * @event append
79306              * @alias Ext.data.Tree#append
79307              */
79308             "append",
79309
79310             /**
79311              * @event remove
79312              * @alias Ext.data.Tree#remove
79313              */
79314             "remove",
79315
79316             /**
79317              * @event move
79318              * @alias Ext.data.Tree#move
79319              */
79320             "move",
79321
79322             /**
79323              * @event insert
79324              * @alias Ext.data.Tree#insert
79325              */
79326             "insert",
79327
79328             /**
79329              * @event beforeappend
79330              * @alias Ext.data.Tree#beforeappend
79331              */
79332             "beforeappend",
79333
79334             /**
79335              * @event beforeremove
79336              * @alias Ext.data.Tree#beforeremove
79337              */
79338             "beforeremove",
79339
79340             /**
79341              * @event beforemove
79342              * @alias Ext.data.Tree#beforemove
79343              */
79344             "beforemove",
79345
79346             /**
79347              * @event beforeinsert
79348              * @alias Ext.data.Tree#beforeinsert
79349              */
79350             "beforeinsert",
79351
79352              /**
79353               * @event expand
79354               * @alias Ext.data.Tree#expand
79355               */
79356              "expand",
79357
79358              /**
79359               * @event collapse
79360               * @alias Ext.data.Tree#collapse
79361               */
79362              "collapse",
79363
79364              /**
79365               * @event beforeexpand
79366               * @alias Ext.data.Tree#beforeexpand
79367               */
79368              "beforeexpand",
79369
79370              /**
79371               * @event beforecollapse
79372               * @alias Ext.data.Tree#beforecollapse
79373               */
79374              "beforecollapse",
79375
79376              /**
79377               * @event rootchange
79378               * @alias Ext.data.Tree#rootchange
79379               */
79380              "rootchange"
79381         ]);
79382
79383         me.tree.on({
79384             scope: me,
79385             remove: me.onNodeRemove,
79386             // this event must follow the relay to beforeitemexpand to allow users to
79387             // cancel the expand:
79388             beforeexpand: me.onBeforeNodeExpand,
79389             beforecollapse: me.onBeforeNodeCollapse,
79390             append: me.onNodeAdded,
79391             insert: me.onNodeAdded
79392         });
79393
79394         me.onBeforeSort();
79395
79396         root = me.root;
79397         if (root) {
79398             delete me.root;
79399             me.setRootNode(root);
79400         }
79401
79402         me.addEvents(
79403             /**
79404              * @event sort
79405              * Fires when this TreeStore is sorted.
79406              * @param {Ext.data.NodeInterface} node The node that is sorted.
79407              */
79408             'sort'
79409         );
79410
79411         if (Ext.isDefined(me.nodeParameter)) {
79412             if (Ext.isDefined(Ext.global.console)) {
79413                 Ext.global.console.warn('Ext.data.TreeStore: nodeParameter has been deprecated. Please use nodeParam instead.');
79414             }
79415             me.nodeParam = me.nodeParameter;
79416             delete me.nodeParameter;
79417         }
79418     },
79419
79420     // inherit docs
79421     setProxy: function(proxy) {
79422         var reader,
79423             needsRoot;
79424
79425         if (proxy instanceof Ext.data.proxy.Proxy) {
79426             // proxy instance, check if a root was set
79427             needsRoot = Ext.isEmpty(proxy.getReader().root);
79428         } else if (Ext.isString(proxy)) {
79429             // string type, means a reader can't be set
79430             needsRoot = true;
79431         } else {
79432             // object, check if a reader and a root were specified.
79433             reader = proxy.reader;
79434             needsRoot = !(reader && !Ext.isEmpty(reader.root));
79435         }
79436         proxy = this.callParent(arguments);
79437         if (needsRoot) {
79438             reader = proxy.getReader();
79439             reader.root = this.defaultRootProperty;
79440             // force rebuild
79441             reader.buildExtractors(true);
79442         }
79443     },
79444
79445     // inherit docs
79446     onBeforeSort: function() {
79447         if (this.folderSort) {
79448             this.sort({
79449                 property: 'leaf',
79450                 direction: 'ASC'
79451             }, 'prepend', false);
79452         }
79453     },
79454
79455     /**
79456      * Called before a node is expanded.
79457      * @private
79458      * @param {Ext.data.NodeInterface} node The node being expanded.
79459      * @param {Function} callback The function to run after the expand finishes
79460      * @param {Object} scope The scope in which to run the callback function
79461      */
79462     onBeforeNodeExpand: function(node, callback, scope) {
79463         if (node.isLoaded()) {
79464             Ext.callback(callback, scope || node, [node.childNodes]);
79465         }
79466         else if (node.isLoading()) {
79467             this.on('load', function() {
79468                 Ext.callback(callback, scope || node, [node.childNodes]);
79469             }, this, {single: true});
79470         }
79471         else {
79472             this.read({
79473                 node: node,
79474                 callback: function() {
79475                     Ext.callback(callback, scope || node, [node.childNodes]);
79476                 }
79477             });
79478         }
79479     },
79480
79481     //inherit docs
79482     getNewRecords: function() {
79483         return Ext.Array.filter(this.tree.flatten(), this.filterNew);
79484     },
79485
79486     //inherit docs
79487     getUpdatedRecords: function() {
79488         return Ext.Array.filter(this.tree.flatten(), this.filterUpdated);
79489     },
79490
79491     /**
79492      * Called before a node is collapsed.
79493      * @private
79494      * @param {Ext.data.NodeInterface} node The node being collapsed.
79495      * @param {Function} callback The function to run after the collapse finishes
79496      * @param {Object} scope The scope in which to run the callback function
79497      */
79498     onBeforeNodeCollapse: function(node, callback, scope) {
79499         callback.call(scope || node, node.childNodes);
79500     },
79501
79502     onNodeRemove: function(parent, node) {
79503         var removed = this.removed;
79504
79505         if (!node.isReplace && Ext.Array.indexOf(removed, node) == -1) {
79506             removed.push(node);
79507         }
79508     },
79509
79510     onNodeAdded: function(parent, node) {
79511         var proxy = this.getProxy(),
79512             reader = proxy.getReader(),
79513             data = node.raw || node.data,
79514             dataRoot, children;
79515
79516         Ext.Array.remove(this.removed, node);
79517
79518         if (!node.isLeaf() && !node.isLoaded()) {
79519             dataRoot = reader.getRoot(data);
79520             if (dataRoot) {
79521                 this.fillNode(node, reader.extractData(dataRoot));
79522                 delete data[reader.root];
79523             }
79524         }
79525     },
79526
79527     /**
79528      * Sets the root node for this store.  See also the {@link #root} config option.
79529      * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
79530      * @return {Ext.data.NodeInterface} The new root
79531      */
79532     setRootNode: function(root) {
79533         var me = this;
79534
79535         root = root || {};
79536         if (!root.isNode) {
79537             // create a default rootNode and create internal data struct.
79538             Ext.applyIf(root, {
79539                 id: me.defaultRootId,
79540                 text: 'Root',
79541                 allowDrag: false
79542             });
79543             root = Ext.ModelManager.create(root, me.model);
79544         }
79545         Ext.data.NodeInterface.decorate(root);
79546
79547         // Because we have decorated the model with new fields,
79548         // we need to build new extactor functions on the reader.
79549         me.getProxy().getReader().buildExtractors(true);
79550
79551         // When we add the root to the tree, it will automaticaly get the NodeInterface
79552         me.tree.setRootNode(root);
79553
79554         // If the user has set expanded: true on the root, we want to call the expand function
79555         if (!root.isLoaded() && (me.autoLoad === true || root.isExpanded())) {
79556             me.load({
79557                 node: root
79558             });
79559         }
79560
79561         return root;
79562     },
79563
79564     /**
79565      * Returns the root node for this tree.
79566      * @return {Ext.data.NodeInterface}
79567      */
79568     getRootNode: function() {
79569         return this.tree.getRootNode();
79570     },
79571
79572     /**
79573      * Returns the record node by id
79574      * @return {Ext.data.NodeInterface}
79575      */
79576     getNodeById: function(id) {
79577         return this.tree.getNodeById(id);
79578     },
79579
79580     /**
79581      * Loads the Store using its configured {@link #proxy}.
79582      * @param {Object} options (Optional) config object. This is passed into the {@link Ext.data.Operation Operation}
79583      * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
79584      * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
79585      * default to the root node.
79586      */
79587     load: function(options) {
79588         options = options || {};
79589         options.params = options.params || {};
79590
79591         var me = this,
79592             node = options.node || me.tree.getRootNode(),
79593             root;
79594
79595         // If there is not a node it means the user hasnt defined a rootnode yet. In this case lets just
79596         // create one for them.
79597         if (!node) {
79598             node = me.setRootNode({
79599                 expanded: true
79600             });
79601         }
79602
79603         if (me.clearOnLoad) {
79604             node.removeAll(true);
79605         }
79606
79607         Ext.applyIf(options, {
79608             node: node
79609         });
79610         options.params[me.nodeParam] = node ? node.getId() : 'root';
79611
79612         if (node) {
79613             node.set('loading', true);
79614         }
79615
79616         return me.callParent([options]);
79617     },
79618
79619
79620     /**
79621      * Fills a node with a series of child records.
79622      * @private
79623      * @param {Ext.data.NodeInterface} node The node to fill
79624      * @param {Ext.data.Model[]} records The records to add
79625      */
79626     fillNode: function(node, records) {
79627         var me = this,
79628             ln = records ? records.length : 0,
79629             i = 0, sortCollection;
79630
79631         if (ln && me.sortOnLoad && !me.remoteSort && me.sorters && me.sorters.items) {
79632             sortCollection = Ext.create('Ext.util.MixedCollection');
79633             sortCollection.addAll(records);
79634             sortCollection.sort(me.sorters.items);
79635             records = sortCollection.items;
79636         }
79637
79638         node.set('loaded', true);
79639         for (; i < ln; i++) {
79640             node.appendChild(records[i], undefined, true);
79641         }
79642
79643         return records;
79644     },
79645
79646     // inherit docs
79647     onProxyLoad: function(operation) {
79648         var me = this,
79649             successful = operation.wasSuccessful(),
79650             records = operation.getRecords(),
79651             node = operation.node;
79652
79653         me.loading = false;
79654         node.set('loading', false);
79655         if (successful) {
79656             records = me.fillNode(node, records);
79657         }
79658         // The load event has an extra node parameter
79659         // (differing from the load event described in AbstractStore)
79660         /**
79661          * @event load
79662          * Fires whenever the store reads data from a remote data source.
79663          * @param {Ext.data.TreeStore} this
79664          * @param {Ext.data.NodeInterface} node The node that was loaded.
79665          * @param {Ext.data.Model[]} records An array of records.
79666          * @param {Boolean} successful True if the operation was successful.
79667          */
79668         // deprecate read?
79669         me.fireEvent('read', me, operation.node, records, successful);
79670         me.fireEvent('load', me, operation.node, records, successful);
79671         //this is a callback that would have been passed to the 'read' function and is optional
79672         Ext.callback(operation.callback, operation.scope || me, [records, operation, successful]);
79673     },
79674
79675     /**
79676      * Creates any new records when a write is returned from the server.
79677      * @private
79678      * @param {Ext.data.Model[]} records The array of new records
79679      * @param {Ext.data.Operation} operation The operation that just completed
79680      * @param {Boolean} success True if the operation was successful
79681      */
79682     onCreateRecords: function(records, operation, success) {
79683         if (success) {
79684             var i = 0,
79685                 length = records.length,
79686                 originalRecords = operation.records,
79687                 parentNode,
79688                 record,
79689                 original,
79690                 index;
79691
79692             /*
79693              * Loop over each record returned from the server. Assume they are
79694              * returned in order of how they were sent. If we find a matching
79695              * record, replace it with the newly created one.
79696              */
79697             for (; i < length; ++i) {
79698                 record = records[i];
79699                 original = originalRecords[i];
79700                 if (original) {
79701                     parentNode = original.parentNode;
79702                     if (parentNode) {
79703                         // prevent being added to the removed cache
79704                         original.isReplace = true;
79705                         parentNode.replaceChild(record, original);
79706                         delete original.isReplace;
79707                     }
79708                     record.phantom = false;
79709                 }
79710             }
79711         }
79712     },
79713
79714     /**
79715      * Updates any records when a write is returned from the server.
79716      * @private
79717      * @param {Ext.data.Model[]} records The array of updated records
79718      * @param {Ext.data.Operation} operation The operation that just completed
79719      * @param {Boolean} success True if the operation was successful
79720      */
79721     onUpdateRecords: function(records, operation, success){
79722         if (success) {
79723             var me = this,
79724                 i = 0,
79725                 length = records.length,
79726                 data = me.data,
79727                 original,
79728                 parentNode,
79729                 record;
79730
79731             for (; i < length; ++i) {
79732                 record = records[i];
79733                 original = me.tree.getNodeById(record.getId());
79734                 parentNode = original.parentNode;
79735                 if (parentNode) {
79736                     // prevent being added to the removed cache
79737                     original.isReplace = true;
79738                     parentNode.replaceChild(record, original);
79739                     original.isReplace = false;
79740                 }
79741             }
79742         }
79743     },
79744
79745     /**
79746      * Removes any records when a write is returned from the server.
79747      * @private
79748      * @param {Ext.data.Model[]} records The array of removed records
79749      * @param {Ext.data.Operation} operation The operation that just completed
79750      * @param {Boolean} success True if the operation was successful
79751      */
79752     onDestroyRecords: function(records, operation, success){
79753         if (success) {
79754             this.removed = [];
79755         }
79756     },
79757
79758     // inherit docs
79759     removeAll: function() {
79760         this.getRootNode().destroy(true);
79761         this.fireEvent('clear', this);
79762     },
79763
79764     // inherit docs
79765     doSort: function(sorterFn) {
79766         var me = this;
79767         if (me.remoteSort) {
79768             //the load function will pick up the new sorters and request the sorted data from the proxy
79769             me.load();
79770         } else {
79771             me.tree.sort(sorterFn, true);
79772             me.fireEvent('datachanged', me);
79773         }
79774         me.fireEvent('sort', me);
79775     }
79776 });
79777
79778 /**
79779  * @extend Ext.data.IdGenerator
79780  * @author Don Griffin
79781  *
79782  * This class generates UUID's according to RFC 4122. This class has a default id property.
79783  * This means that a single instance is shared unless the id property is overridden. Thus,
79784  * two {@link Ext.data.Model} instances configured like the following share one generator:
79785  *
79786  *     Ext.define('MyApp.data.MyModelX', {
79787  *         extend: 'Ext.data.Model',
79788  *         idgen: 'uuid'
79789  *     });
79790  *
79791  *     Ext.define('MyApp.data.MyModelY', {
79792  *         extend: 'Ext.data.Model',
79793  *         idgen: 'uuid'
79794  *     });
79795  *
79796  * This allows all models using this class to share a commonly configured instance.
79797  *
79798  * # Using Version 1 ("Sequential") UUID's
79799  *
79800  * If a server can provide a proper timestamp and a "cryptographic quality random number"
79801  * (as described in RFC 4122), the shared instance can be configured as follows:
79802  *
79803  *     Ext.data.IdGenerator.get('uuid').reconfigure({
79804  *         version: 1,
79805  *         clockSeq: clock, // 14 random bits
79806  *         salt: salt,      // 48 secure random bits (the Node field)
79807  *         timestamp: ts    // timestamp per Section 4.1.4
79808  *     });
79809  *
79810  *     // or these values can be split into 32-bit chunks:
79811  *
79812  *     Ext.data.IdGenerator.get('uuid').reconfigure({
79813  *         version: 1,
79814  *         clockSeq: clock,
79815  *         salt: { lo: saltLow32, hi: saltHigh32 },
79816  *         timestamp: { lo: timestampLow32, hi: timestamptHigh32 }
79817  *     });
79818  *
79819  * This approach improves the generator's uniqueness by providing a valid timestamp and
79820  * higher quality random data. Version 1 UUID's should not be used unless this information
79821  * can be provided by a server and care should be taken to avoid caching of this data.
79822  *
79823  * See http://www.ietf.org/rfc/rfc4122.txt for details.
79824  */
79825 Ext.define('Ext.data.UuidGenerator', function () {
79826     var twoPow14 = Math.pow(2, 14),
79827         twoPow16 = Math.pow(2, 16),
79828         twoPow28 = Math.pow(2, 28),
79829         twoPow32 = Math.pow(2, 32);
79830
79831     function toHex (value, length) {
79832         var ret = value.toString(16);
79833         if (ret.length > length) {
79834             ret = ret.substring(ret.length - length); // right-most digits
79835         } else if (ret.length < length) {
79836             ret = Ext.String.leftPad(ret, length, '0');
79837         }
79838         return ret;
79839     }
79840
79841     function rand (lo, hi) {
79842         var v = Math.random() * (hi - lo + 1);
79843         return Math.floor(v) + lo;
79844     }
79845
79846     function split (bignum) {
79847         if (typeof(bignum) == 'number') {
79848             var hi = Math.floor(bignum / twoPow32);
79849             return {
79850                 lo: Math.floor(bignum - hi * twoPow32),
79851                 hi: hi
79852             };
79853         }
79854         return bignum;
79855     }
79856
79857     return {
79858         extend: 'Ext.data.IdGenerator',
79859
79860         alias: 'idgen.uuid',
79861
79862         id: 'uuid', // shared by default
79863
79864         /**
79865          * @property {Number/Object} salt
79866          * When created, this value is a 48-bit number. For computation, this value is split
79867          * into 32-bit parts and stored in an object with `hi` and `lo` properties.
79868          */
79869
79870         /**
79871          * @property {Number/Object} timestamp
79872          * When created, this value is a 60-bit number. For computation, this value is split
79873          * into 32-bit parts and stored in an object with `hi` and `lo` properties.
79874          */
79875
79876         /**
79877          * @cfg {Number} version
79878          * The Version of UUID. Supported values are:
79879          *
79880          *  * 1 : Time-based, "sequential" UUID.
79881          *  * 4 : Pseudo-random UUID.
79882          *
79883          * The default is 4.
79884          */
79885         version: 4,
79886
79887         constructor: function() {
79888             var me = this;
79889
79890             me.callParent(arguments);
79891
79892             me.parts = [];
79893             me.init();
79894         },
79895
79896         generate: function () {
79897             var me = this,
79898                 parts = me.parts,
79899                 ts = me.timestamp;
79900
79901             /*
79902                The magic decoder ring (derived from RFC 4122 Section 4.2.2):
79903
79904                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79905                |                          time_low                             |
79906                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79907                |           time_mid            |  ver  |        time_hi        |
79908                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79909                |res|  clock_hi |   clock_low   |    salt 0   |M|     salt 1    |
79910                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79911                |                         salt (2-5)                            |
79912                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79913
79914                          time_mid      clock_hi (low 6 bits)
79915                 time_low     | time_hi |clock_lo
79916                     |        |     |   || salt[0]
79917                     |        |     |   ||   | salt[1..5]
79918                     v        v     v   vv   v v
79919                     0badf00d-aced-1def-b123-dfad0badbeef
79920                                   ^    ^     ^
79921                             version    |     multicast (low bit)
79922                                        |
79923                                     reserved (upper 2 bits)
79924             */
79925             parts[0] = toHex(ts.lo, 8);
79926             parts[1] = toHex(ts.hi & 0xFFFF, 4);
79927             parts[2] = toHex(((ts.hi >>> 16) & 0xFFF) | (me.version << 12), 4);
79928             parts[3] = toHex(0x80 | ((me.clockSeq >>> 8) & 0x3F), 2) +
79929                        toHex(me.clockSeq & 0xFF, 2);
79930             parts[4] = toHex(me.salt.hi, 4) + toHex(me.salt.lo, 8);
79931
79932             if (me.version == 4) {
79933                 me.init(); // just regenerate all the random values...
79934             } else {
79935                 // sequentially increment the timestamp...
79936                 ++ts.lo;
79937                 if (ts.lo >= twoPow32) { // if (overflow)
79938                     ts.lo = 0;
79939                     ++ts.hi;
79940                 }
79941             }
79942
79943             return parts.join('-').toLowerCase();
79944         },
79945
79946         getRecId: function (rec) {
79947             return rec.getId();
79948         },
79949
79950         /**
79951          * @private
79952          */
79953         init: function () {
79954             var me = this,
79955                 salt, time;
79956
79957             if (me.version == 4) {
79958                 // See RFC 4122 (Secion 4.4)
79959                 //   o  If the state was unavailable (e.g., non-existent or corrupted),
79960                 //      or the saved node ID is different than the current node ID,
79961                 //      generate a random clock sequence value.
79962                 me.clockSeq = rand(0, twoPow14-1);
79963
79964                 // we run this on every id generation...
79965                 salt = me.salt || (me.salt = {});
79966                 time = me.timestamp || (me.timestamp = {});
79967
79968                 // See RFC 4122 (Secion 4.4)
79969                 salt.lo = rand(0, twoPow32-1);
79970                 salt.hi = rand(0, twoPow16-1);
79971                 time.lo = rand(0, twoPow32-1);
79972                 time.hi = rand(0, twoPow28-1);
79973             } else {
79974                 // this is run only once per-instance
79975                 me.salt = split(me.salt);
79976                 me.timestamp = split(me.timestamp);
79977
79978                 // Set multicast bit: "the least significant bit of the first octet of the
79979                 // node ID" (nodeId = salt for this implementation):
79980                 me.salt.hi |= 0x100;
79981             }
79982         },
79983
79984         /**
79985          * Reconfigures this generator given new config properties.
79986          */
79987         reconfigure: function (config) {
79988             Ext.apply(this, config);
79989             this.init();
79990         }
79991     };
79992 }());
79993
79994 /**
79995  * @author Ed Spencer
79996  * @class Ext.data.XmlStore
79997  * @extends Ext.data.Store
79998  * @private
79999  * @ignore
80000  * <p>Small helper class to make creating {@link Ext.data.Store}s from XML data easier.
80001  * A XmlStore will be automatically configured with a {@link Ext.data.reader.Xml}.</p>
80002  * <p>A store configuration would be something like:<pre><code>
80003 var store = new Ext.data.XmlStore({
80004     // store configs
80005     autoDestroy: true,
80006     storeId: 'myStore',
80007     url: 'sheldon.xml', // automatically configures a HttpProxy
80008     // reader configs
80009     record: 'Item', // records will have an "Item" tag
80010     idPath: 'ASIN',
80011     totalRecords: '@TotalResults'
80012     fields: [
80013         // set up the fields mapping into the xml doc
80014         // The first needs mapping, the others are very basic
80015         {name: 'Author', mapping: 'ItemAttributes > Author'},
80016         'Title', 'Manufacturer', 'ProductGroup'
80017     ]
80018 });
80019  * </code></pre></p>
80020  * <p>This store is configured to consume a returned object of the form:<pre><code>
80021 &#60?xml version="1.0" encoding="UTF-8"?>
80022 &#60ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2009-05-15">
80023     &#60Items>
80024         &#60Request>
80025             &#60IsValid>True&#60/IsValid>
80026             &#60ItemSearchRequest>
80027                 &#60Author>Sidney Sheldon&#60/Author>
80028                 &#60SearchIndex>Books&#60/SearchIndex>
80029             &#60/ItemSearchRequest>
80030         &#60/Request>
80031         &#60TotalResults>203&#60/TotalResults>
80032         &#60TotalPages>21&#60/TotalPages>
80033         &#60Item>
80034             &#60ASIN>0446355453&#60/ASIN>
80035             &#60DetailPageURL>
80036                 http://www.amazon.com/
80037             &#60/DetailPageURL>
80038             &#60ItemAttributes>
80039                 &#60Author>Sidney Sheldon&#60/Author>
80040                 &#60Manufacturer>Warner Books&#60/Manufacturer>
80041                 &#60ProductGroup>Book&#60/ProductGroup>
80042                 &#60Title>Master of the Game&#60/Title>
80043             &#60/ItemAttributes>
80044         &#60/Item>
80045     &#60/Items>
80046 &#60/ItemSearchResponse>
80047  * </code></pre>
80048  * An object literal of this form could also be used as the {@link #data} config option.</p>
80049  * <p><b>Note:</b> This class accepts all of the configuration options of
80050  * <b>{@link Ext.data.reader.Xml XmlReader}</b>.</p>
80051  * @xtype xmlstore
80052  */
80053 Ext.define('Ext.data.XmlStore', {
80054     extend: 'Ext.data.Store',
80055     alternateClassName: 'Ext.data.XmlStore',
80056     alias: 'store.xml',
80057
80058     /**
80059      * @cfg {Ext.data.DataReader} reader @hide
80060      */
80061     constructor: function(config){
80062         config = config || {};
80063         config = config || {};
80064
80065         Ext.applyIf(config, {
80066             proxy: {
80067                 type: 'ajax',
80068                 reader: 'xml',
80069                 writer: 'xml'
80070             }
80071         });
80072
80073         this.callParent([config]);
80074     }
80075 });
80076
80077 /**
80078  * @author Ed Spencer
80079  *
80080  * Base class for any client-side storage. Used as a superclass for {@link Ext.data.proxy.Memory Memory} and
80081  * {@link Ext.data.proxy.WebStorage Web Storage} proxies. Do not use directly, use one of the subclasses instead.
80082  * @private
80083  */
80084 Ext.define('Ext.data.proxy.Client', {
80085     extend: 'Ext.data.proxy.Proxy',
80086     alternateClassName: 'Ext.data.ClientProxy',
80087
80088     /**
80089      * Abstract function that must be implemented by each ClientProxy subclass. This should purge all record data
80090      * from the client side storage, as well as removing any supporting data (such as lists of record IDs)
80091      */
80092     clear: function() {
80093     }
80094 });
80095 /**
80096  * @author Ed Spencer
80097  *
80098  * The JsonP proxy is useful when you need to load data from a domain other than the one your application is running on. If
80099  * your application is running on http://domainA.com it cannot use {@link Ext.data.proxy.Ajax Ajax} to load its data
80100  * from http://domainB.com because cross-domain ajax requests are prohibited by the browser.
80101  *
80102  * We can get around this using a JsonP proxy. JsonP proxy injects a `<script>` tag into the DOM whenever an AJAX request
80103  * would usually be made. Let's say we want to load data from http://domainB.com/users - the script tag that would be
80104  * injected might look like this:
80105  *
80106  *     <script src="http://domainB.com/users?callback=someCallback"></script>
80107  *
80108  * When we inject the tag above, the browser makes a request to that url and includes the response as if it was any
80109  * other type of JavaScript include. By passing a callback in the url above, we're telling domainB's server that we want
80110  * to be notified when the result comes in and that it should call our callback function with the data it sends back. So
80111  * long as the server formats the response to look like this, everything will work:
80112  *
80113  *     someCallback({
80114  *         users: [
80115  *             {
80116  *                 id: 1,
80117  *                 name: "Ed Spencer",
80118  *                 email: "ed@sencha.com"
80119  *             }
80120  *         ]
80121  *     });
80122  *
80123  * As soon as the script finishes loading, the 'someCallback' function that we passed in the url is called with the JSON
80124  * object that the server returned.
80125  *
80126  * JsonP proxy takes care of all of this automatically. It formats the url you pass, adding the callback parameter
80127  * automatically. It even creates a temporary callback function, waits for it to be called and then puts the data into
80128  * the Proxy making it look just like you loaded it through a normal {@link Ext.data.proxy.Ajax AjaxProxy}. Here's how
80129  * we might set that up:
80130  *
80131  *     Ext.define('User', {
80132  *         extend: 'Ext.data.Model',
80133  *         fields: ['id', 'name', 'email']
80134  *     });
80135  *
80136  *     var store = Ext.create('Ext.data.Store', {
80137  *         model: 'User',
80138  *         proxy: {
80139  *             type: 'jsonp',
80140  *             url : 'http://domainB.com/users'
80141  *         }
80142  *     });
80143  *
80144  *     store.load();
80145  *
80146  * 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
80147  * like this:
80148  *
80149  *     <script src="http://domainB.com/users?callback=callback1"></script>
80150  *
80151  * # Customization
80152  *
80153  * This script tag can be customized using the {@link #callbackKey} configuration. For example:
80154  *
80155  *     var store = Ext.create('Ext.data.Store', {
80156  *         model: 'User',
80157  *         proxy: {
80158  *             type: 'jsonp',
80159  *             url : 'http://domainB.com/users',
80160  *             callbackKey: 'theCallbackFunction'
80161  *         }
80162  *     });
80163  *
80164  *     store.load();
80165  *
80166  * Would inject a script tag like this:
80167  *
80168  *     <script src="http://domainB.com/users?theCallbackFunction=callback1"></script>
80169  *
80170  * # Implementing on the server side
80171  *
80172  * The remote server side needs to be configured to return data in this format. Here are suggestions for how you might
80173  * achieve this using Java, PHP and ASP.net:
80174  *
80175  * Java:
80176  *
80177  *     boolean jsonP = false;
80178  *     String cb = request.getParameter("callback");
80179  *     if (cb != null) {
80180  *         jsonP = true;
80181  *         response.setContentType("text/javascript");
80182  *     } else {
80183  *         response.setContentType("application/x-json");
80184  *     }
80185  *     Writer out = response.getWriter();
80186  *     if (jsonP) {
80187  *         out.write(cb + "(");
80188  *     }
80189  *     out.print(dataBlock.toJsonString());
80190  *     if (jsonP) {
80191  *         out.write(");");
80192  *     }
80193  *
80194  * PHP:
80195  *
80196  *     $callback = $_REQUEST['callback'];
80197  *
80198  *     // Create the output object.
80199  *     $output = array('a' => 'Apple', 'b' => 'Banana');
80200  *
80201  *     //start output
80202  *     if ($callback) {
80203  *         header('Content-Type: text/javascript');
80204  *         echo $callback . '(' . json_encode($output) . ');';
80205  *     } else {
80206  *         header('Content-Type: application/x-json');
80207  *         echo json_encode($output);
80208  *     }
80209  *
80210  * ASP.net:
80211  *
80212  *     String jsonString = "{success: true}";
80213  *     String cb = Request.Params.Get("callback");
80214  *     String responseString = "";
80215  *     if (!String.IsNullOrEmpty(cb)) {
80216  *         responseString = cb + "(" + jsonString + ")";
80217  *     } else {
80218  *         responseString = jsonString;
80219  *     }
80220  *     Response.Write(responseString);
80221  */
80222 Ext.define('Ext.data.proxy.JsonP', {
80223     extend: 'Ext.data.proxy.Server',
80224     alternateClassName: 'Ext.data.ScriptTagProxy',
80225     alias: ['proxy.jsonp', 'proxy.scripttag'],
80226     requires: ['Ext.data.JsonP'],
80227
80228     defaultWriterType: 'base',
80229
80230     /**
80231      * @cfg {String} callbackKey
80232      * See {@link Ext.data.JsonP#callbackKey}.
80233      */
80234     callbackKey : 'callback',
80235
80236     /**
80237      * @cfg {String} recordParam
80238      * The param name to use when passing records to the server (e.g. 'records=someEncodedRecordString'). Defaults to
80239      * 'records'
80240      */
80241     recordParam: 'records',
80242
80243     /**
80244      * @cfg {Boolean} autoAppendParams
80245      * True to automatically append the request's params to the generated url. Defaults to true
80246      */
80247     autoAppendParams: true,
80248
80249     constructor: function(){
80250         this.addEvents(
80251             /**
80252              * @event
80253              * Fires when the server returns an exception
80254              * @param {Ext.data.proxy.Proxy} this
80255              * @param {Ext.data.Request} request The request that was sent
80256              * @param {Ext.data.Operation} operation The operation that triggered the request
80257              */
80258             'exception'
80259         );
80260         this.callParent(arguments);
80261     },
80262
80263     /**
80264      * @private
80265      * Performs the read request to the remote domain. JsonP proxy does not actually create an Ajax request,
80266      * instead we write out a <script> tag based on the configuration of the internal Ext.data.Request object
80267      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
80268      * @param {Function} callback A callback function to execute when the Operation has been completed
80269      * @param {Object} scope The scope to execute the callback in
80270      */
80271     doRequest: function(operation, callback, scope) {
80272         //generate the unique IDs for this request
80273         var me      = this,
80274             writer  = me.getWriter(),
80275             request = me.buildRequest(operation),
80276             params = request.params;
80277
80278         if (operation.allowWrite()) {
80279             request = writer.write(request);
80280         }
80281
80282         // apply JsonP proxy-specific attributes to the Request
80283         Ext.apply(request, {
80284             callbackKey: me.callbackKey,
80285             timeout: me.timeout,
80286             scope: me,
80287             disableCaching: false, // handled by the proxy
80288             callback: me.createRequestCallback(request, operation, callback, scope)
80289         });
80290
80291         // prevent doubling up
80292         if (me.autoAppendParams) {
80293             request.params = {};
80294         }
80295
80296         request.jsonp = Ext.data.JsonP.request(request);
80297         // restore on the request
80298         request.params = params;
80299         operation.setStarted();
80300         me.lastRequest = request;
80301
80302         return request;
80303     },
80304
80305     /**
80306      * @private
80307      * Creates and returns the function that is called when the request has completed. The returned function
80308      * should accept a Response object, which contains the response to be read by the configured Reader.
80309      * The third argument is the callback that should be called after the request has been completed and the Reader has decoded
80310      * the response. This callback will typically be the callback passed by a store, e.g. in proxy.read(operation, theCallback, scope)
80311      * theCallback refers to the callback argument received by this function.
80312      * See {@link #doRequest} for details.
80313      * @param {Ext.data.Request} request The Request object
80314      * @param {Ext.data.Operation} operation The Operation being executed
80315      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
80316      * passed to doRequest
80317      * @param {Object} scope The scope in which to execute the callback function
80318      * @return {Function} The callback function
80319      */
80320     createRequestCallback: function(request, operation, callback, scope) {
80321         var me = this;
80322
80323         return function(success, response, errorType) {
80324             delete me.lastRequest;
80325             me.processResponse(success, operation, request, response, callback, scope);
80326         };
80327     },
80328
80329     // inherit docs
80330     setException: function(operation, response) {
80331         operation.setException(operation.request.jsonp.errorType);
80332     },
80333
80334
80335     /**
80336      * Generates a url based on a given Ext.data.Request object. Adds the params and callback function name to the url
80337      * @param {Ext.data.Request} request The request object
80338      * @return {String} The url
80339      */
80340     buildUrl: function(request) {
80341         var me      = this,
80342             url     = me.callParent(arguments),
80343             params  = Ext.apply({}, request.params),
80344             filters = params.filters,
80345             records,
80346             filter, i;
80347
80348         delete params.filters;
80349
80350         if (me.autoAppendParams) {
80351             url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
80352         }
80353
80354         if (filters && filters.length) {
80355             for (i = 0; i < filters.length; i++) {
80356                 filter = filters[i];
80357
80358                 if (filter.value) {
80359                     url = Ext.urlAppend(url, filter.property + "=" + filter.value);
80360                 }
80361             }
80362         }
80363
80364         //if there are any records present, append them to the url also
80365         records = request.records;
80366
80367         if (Ext.isArray(records) && records.length > 0) {
80368             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.recordParam, me.encodeRecords(records)));
80369         }
80370
80371         return url;
80372     },
80373
80374     //inherit docs
80375     destroy: function() {
80376         this.abort();
80377         this.callParent();
80378     },
80379
80380     /**
80381      * Aborts the current server request if one is currently running
80382      */
80383     abort: function() {
80384         var lastRequest = this.lastRequest;
80385         if (lastRequest) {
80386             Ext.data.JsonP.abort(lastRequest.jsonp);
80387         }
80388     },
80389
80390     /**
80391      * Encodes an array of records into a string suitable to be appended to the script src url. This is broken out into
80392      * its own function so that it can be easily overridden.
80393      * @param {Ext.data.Model[]} records The records array
80394      * @return {String} The encoded records string
80395      */
80396     encodeRecords: function(records) {
80397         var encoded = "",
80398             i = 0,
80399             len = records.length;
80400
80401         for (; i < len; i++) {
80402             encoded += Ext.Object.toQueryString(records[i].data);
80403         }
80404
80405         return encoded;
80406     }
80407 });
80408
80409 /**
80410  * @author Ed Spencer
80411  *
80412  * WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage LocalStorage} and {@link
80413  * Ext.data.proxy.SessionStorage SessionStorage} proxies. It uses the new HTML5 key/value client-side storage objects to
80414  * save {@link Ext.data.Model model instances} for offline use.
80415  * @private
80416  */
80417 Ext.define('Ext.data.proxy.WebStorage', {
80418     extend: 'Ext.data.proxy.Client',
80419     alternateClassName: 'Ext.data.WebStorageProxy',
80420
80421     /**
80422      * @cfg {String} id
80423      * The unique ID used as the key in which all record data are stored in the local storage object.
80424      */
80425     id: undefined,
80426
80427     /**
80428      * Creates the proxy, throws an error if local storage is not supported in the current browser.
80429      * @param {Object} config (optional) Config object.
80430      */
80431     constructor: function(config) {
80432         this.callParent(arguments);
80433
80434         /**
80435          * @property {Object} cache
80436          * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.
80437          */
80438         this.cache = {};
80439
80440
80441         //if an id is not given, try to use the store's id instead
80442         this.id = this.id || (this.store ? this.store.storeId : undefined);
80443
80444
80445         this.initialize();
80446     },
80447
80448     //inherit docs
80449     create: function(operation, callback, scope) {
80450         var records = operation.records,
80451             length  = records.length,
80452             ids     = this.getIds(),
80453             id, record, i;
80454
80455         operation.setStarted();
80456
80457         for (i = 0; i < length; i++) {
80458             record = records[i];
80459
80460             if (record.phantom) {
80461                 record.phantom = false;
80462                 id = this.getNextId();
80463             } else {
80464                 id = record.getId();
80465             }
80466
80467             this.setRecord(record, id);
80468             ids.push(id);
80469         }
80470
80471         this.setIds(ids);
80472
80473         operation.setCompleted();
80474         operation.setSuccessful();
80475
80476         if (typeof callback == 'function') {
80477             callback.call(scope || this, operation);
80478         }
80479     },
80480
80481     //inherit docs
80482     read: function(operation, callback, scope) {
80483         //TODO: respect sorters, filters, start and limit options on the Operation
80484
80485         var records = [],
80486             ids     = this.getIds(),
80487             length  = ids.length,
80488             i, recordData, record;
80489
80490         //read a single record
80491         if (operation.id) {
80492             record = this.getRecord(operation.id);
80493
80494             if (record) {
80495                 records.push(record);
80496                 operation.setSuccessful();
80497             }
80498         } else {
80499             for (i = 0; i < length; i++) {
80500                 records.push(this.getRecord(ids[i]));
80501             }
80502             operation.setSuccessful();
80503         }
80504
80505         operation.setCompleted();
80506
80507         operation.resultSet = Ext.create('Ext.data.ResultSet', {
80508             records: records,
80509             total  : records.length,
80510             loaded : true
80511         });
80512
80513         if (typeof callback == 'function') {
80514             callback.call(scope || this, operation);
80515         }
80516     },
80517
80518     //inherit docs
80519     update: function(operation, callback, scope) {
80520         var records = operation.records,
80521             length  = records.length,
80522             ids     = this.getIds(),
80523             record, id, i;
80524
80525         operation.setStarted();
80526
80527         for (i = 0; i < length; i++) {
80528             record = records[i];
80529             this.setRecord(record);
80530
80531             //we need to update the set of ids here because it's possible that a non-phantom record was added
80532             //to this proxy - in which case the record's id would never have been added via the normal 'create' call
80533             id = record.getId();
80534             if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
80535                 ids.push(id);
80536             }
80537         }
80538         this.setIds(ids);
80539
80540         operation.setCompleted();
80541         operation.setSuccessful();
80542
80543         if (typeof callback == 'function') {
80544             callback.call(scope || this, operation);
80545         }
80546     },
80547
80548     //inherit
80549     destroy: function(operation, callback, scope) {
80550         var records = operation.records,
80551             length  = records.length,
80552             ids     = this.getIds(),
80553
80554             //newIds is a copy of ids, from which we remove the destroyed records
80555             newIds  = [].concat(ids),
80556             i;
80557
80558         for (i = 0; i < length; i++) {
80559             Ext.Array.remove(newIds, records[i].getId());
80560             this.removeRecord(records[i], false);
80561         }
80562
80563         this.setIds(newIds);
80564
80565         operation.setCompleted();
80566         operation.setSuccessful();
80567
80568         if (typeof callback == 'function') {
80569             callback.call(scope || this, operation);
80570         }
80571     },
80572
80573     /**
80574      * @private
80575      * Fetches a model instance from the Proxy by ID. Runs each field's decode function (if present) to decode the data.
80576      * @param {String} id The record's unique ID
80577      * @return {Ext.data.Model} The model instance
80578      */
80579     getRecord: function(id) {
80580         if (this.cache[id] === undefined) {
80581             var rawData = Ext.decode(this.getStorageObject().getItem(this.getRecordKey(id))),
80582                 data    = {},
80583                 Model   = this.model,
80584                 fields  = Model.prototype.fields.items,
80585                 length  = fields.length,
80586                 i, field, name, record;
80587
80588             for (i = 0; i < length; i++) {
80589                 field = fields[i];
80590                 name  = field.name;
80591
80592                 if (typeof field.decode == 'function') {
80593                     data[name] = field.decode(rawData[name]);
80594                 } else {
80595                     data[name] = rawData[name];
80596                 }
80597             }
80598
80599             record = new Model(data, id);
80600             record.phantom = false;
80601
80602             this.cache[id] = record;
80603         }
80604
80605         return this.cache[id];
80606     },
80607
80608     /**
80609      * Saves the given record in the Proxy. Runs each field's encode function (if present) to encode the data.
80610      * @param {Ext.data.Model} record The model instance
80611      * @param {String} [id] The id to save the record under (defaults to the value of the record's getId() function)
80612      */
80613     setRecord: function(record, id) {
80614         if (id) {
80615             record.setId(id);
80616         } else {
80617             id = record.getId();
80618         }
80619
80620         var me = this,
80621             rawData = record.data,
80622             data    = {},
80623             model   = me.model,
80624             fields  = model.prototype.fields.items,
80625             length  = fields.length,
80626             i = 0,
80627             field, name, obj, key;
80628
80629         for (; i < length; i++) {
80630             field = fields[i];
80631             name  = field.name;
80632
80633             if (typeof field.encode == 'function') {
80634                 data[name] = field.encode(rawData[name], record);
80635             } else {
80636                 data[name] = rawData[name];
80637             }
80638         }
80639
80640         obj = me.getStorageObject();
80641         key = me.getRecordKey(id);
80642
80643         //keep the cache up to date
80644         me.cache[id] = record;
80645
80646         //iPad bug requires that we remove the item before setting it
80647         obj.removeItem(key);
80648         obj.setItem(key, Ext.encode(data));
80649     },
80650
80651     /**
80652      * @private
80653      * Physically removes a given record from the local storage. Used internally by {@link #destroy}, which you should
80654      * use instead because it updates the list of currently-stored record ids
80655      * @param {String/Number/Ext.data.Model} id The id of the record to remove, or an Ext.data.Model instance
80656      */
80657     removeRecord: function(id, updateIds) {
80658         var me = this,
80659             ids;
80660
80661         if (id.isModel) {
80662             id = id.getId();
80663         }
80664
80665         if (updateIds !== false) {
80666             ids = me.getIds();
80667             Ext.Array.remove(ids, id);
80668             me.setIds(ids);
80669         }
80670
80671         me.getStorageObject().removeItem(me.getRecordKey(id));
80672     },
80673
80674     /**
80675      * @private
80676      * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
80677      * storing data in the local storage object and should prevent naming collisions.
80678      * @param {String/Number/Ext.data.Model} id The record id, or a Model instance
80679      * @return {String} The unique key for this record
80680      */
80681     getRecordKey: function(id) {
80682         if (id.isModel) {
80683             id = id.getId();
80684         }
80685
80686         return Ext.String.format("{0}-{1}", this.id, id);
80687     },
80688
80689     /**
80690      * @private
80691      * Returns the unique key used to store the current record counter for this proxy. This is used internally when
80692      * realizing models (creating them when they used to be phantoms), in order to give each model instance a unique id.
80693      * @return {String} The counter key
80694      */
80695     getRecordCounterKey: function() {
80696         return Ext.String.format("{0}-counter", this.id);
80697     },
80698
80699     /**
80700      * @private
80701      * Returns the array of record IDs stored in this Proxy
80702      * @return {Number[]} The record IDs. Each is cast as a Number
80703      */
80704     getIds: function() {
80705         var ids    = (this.getStorageObject().getItem(this.id) || "").split(","),
80706             length = ids.length,
80707             i;
80708
80709         if (length == 1 && ids[0] === "") {
80710             ids = [];
80711         } else {
80712             for (i = 0; i < length; i++) {
80713                 ids[i] = parseInt(ids[i], 10);
80714             }
80715         }
80716
80717         return ids;
80718     },
80719
80720     /**
80721      * @private
80722      * Saves the array of ids representing the set of all records in the Proxy
80723      * @param {Number[]} ids The ids to set
80724      */
80725     setIds: function(ids) {
80726         var obj = this.getStorageObject(),
80727             str = ids.join(",");
80728
80729         obj.removeItem(this.id);
80730
80731         if (!Ext.isEmpty(str)) {
80732             obj.setItem(this.id, str);
80733         }
80734     },
80735
80736     /**
80737      * @private
80738      * Returns the next numerical ID that can be used when realizing a model instance (see getRecordCounterKey).
80739      * Increments the counter.
80740      * @return {Number} The id
80741      */
80742     getNextId: function() {
80743         var obj  = this.getStorageObject(),
80744             key  = this.getRecordCounterKey(),
80745             last = obj.getItem(key),
80746             ids, id;
80747
80748         if (last === null) {
80749             ids = this.getIds();
80750             last = ids[ids.length - 1] || 0;
80751         }
80752
80753         id = parseInt(last, 10) + 1;
80754         obj.setItem(key, id);
80755
80756         return id;
80757     },
80758
80759     /**
80760      * @private
80761      * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
80762      * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
80763      */
80764     initialize: function() {
80765         var storageObject = this.getStorageObject();
80766         storageObject.setItem(this.id, storageObject.getItem(this.id) || "");
80767     },
80768
80769     /**
80770      * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the
80771      * storage object.
80772      */
80773     clear: function() {
80774         var obj = this.getStorageObject(),
80775             ids = this.getIds(),
80776             len = ids.length,
80777             i;
80778
80779         //remove all the records
80780         for (i = 0; i < len; i++) {
80781             this.removeRecord(ids[i]);
80782         }
80783
80784         //remove the supporting objects
80785         obj.removeItem(this.getRecordCounterKey());
80786         obj.removeItem(this.id);
80787     },
80788
80789     /**
80790      * @private
80791      * Abstract function which should return the storage object that data will be saved to. This must be implemented
80792      * in each subclass.
80793      * @return {Object} The storage object
80794      */
80795     getStorageObject: function() {
80796     }
80797 });
80798 /**
80799  * @author Ed Spencer
80800  *
80801  * The LocalStorageProxy uses the new HTML5 localStorage API to save {@link Ext.data.Model Model} data locally on the
80802  * client browser. HTML5 localStorage is a key-value store (e.g. cannot save complex objects like JSON), so
80803  * LocalStorageProxy automatically serializes and deserializes data when saving and retrieving it.
80804  *
80805  * localStorage is extremely useful for saving user-specific information without needing to build server-side
80806  * infrastructure to support it. Let's imagine we're writing a Twitter search application and want to save the user's
80807  * searches locally so they can easily perform a saved search again later. We'd start by creating a Search model:
80808  *
80809  *     Ext.define('Search', {
80810  *         fields: ['id', 'query'],
80811  *         extend: 'Ext.data.Model',
80812  *         proxy: {
80813  *             type: 'localstorage',
80814  *             id  : 'twitter-Searches'
80815  *         }
80816  *     });
80817  *
80818  * Our Search model contains just two fields - id and query - plus a Proxy definition. The only configuration we need to
80819  * pass to the LocalStorage proxy is an {@link #id}. This is important as it separates the Model data in this Proxy from
80820  * all others. The localStorage API puts all data into a single shared namespace, so by setting an id we enable
80821  * LocalStorageProxy to manage the saved Search data.
80822  *
80823  * Saving our data into localStorage is easy and would usually be done with a {@link Ext.data.Store Store}:
80824  *
80825  *     //our Store automatically picks up the LocalStorageProxy defined on the Search model
80826  *     var store = Ext.create('Ext.data.Store', {
80827  *         model: "Search"
80828  *     });
80829  *
80830  *     //loads any existing Search data from localStorage
80831  *     store.load();
80832  *
80833  *     //now add some Searches
80834  *     store.add({query: 'Sencha Touch'});
80835  *     store.add({query: 'Ext JS'});
80836  *
80837  *     //finally, save our Search data to localStorage
80838  *     store.sync();
80839  *
80840  * The LocalStorageProxy automatically gives our new Searches an id when we call store.sync(). It encodes the Model data
80841  * and places it into localStorage. We can also save directly to localStorage, bypassing the Store altogether:
80842  *
80843  *     var search = Ext.create('Search', {query: 'Sencha Animator'});
80844  *
80845  *     //uses the configured LocalStorageProxy to save the new Search to localStorage
80846  *     search.save();
80847  *
80848  * # Limitations
80849  *
80850  * If this proxy is used in a browser where local storage is not supported, the constructor will throw an error. A local
80851  * storage proxy requires a unique ID which is used as a key in which all record data are stored in the local storage
80852  * object.
80853  *
80854  * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
80855  * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
80856  */
80857 Ext.define('Ext.data.proxy.LocalStorage', {
80858     extend: 'Ext.data.proxy.WebStorage',
80859     alias: 'proxy.localstorage',
80860     alternateClassName: 'Ext.data.LocalStorageProxy',
80861     
80862     //inherit docs
80863     getStorageObject: function() {
80864         return window.localStorage;
80865     }
80866 });
80867 /**
80868  * @author Ed Spencer
80869  *
80870  * In-memory proxy. This proxy simply uses a local variable for data storage/retrieval, so its contents are lost on
80871  * every page refresh.
80872  *
80873  * Usually this Proxy isn't used directly, serving instead as a helper to a {@link Ext.data.Store Store} where a reader
80874  * is required to load data. For example, say we have a Store for a User model and have some inline data we want to
80875  * load, but this data isn't in quite the right format: we can use a MemoryProxy with a JsonReader to read it into our
80876  * Store:
80877  *
80878  *     //this is the model we will be using in the store
80879  *     Ext.define('User', {
80880  *         extend: 'Ext.data.Model',
80881  *         fields: [
80882  *             {name: 'id',    type: 'int'},
80883  *             {name: 'name',  type: 'string'},
80884  *             {name: 'phone', type: 'string', mapping: 'phoneNumber'}
80885  *         ]
80886  *     });
80887  *
80888  *     //this data does not line up to our model fields - the phone field is called phoneNumber
80889  *     var data = {
80890  *         users: [
80891  *             {
80892  *                 id: 1,
80893  *                 name: 'Ed Spencer',
80894  *                 phoneNumber: '555 1234'
80895  *             },
80896  *             {
80897  *                 id: 2,
80898  *                 name: 'Abe Elias',
80899  *                 phoneNumber: '666 1234'
80900  *             }
80901  *         ]
80902  *     };
80903  *
80904  *     //note how we set the 'root' in the reader to match the data structure above
80905  *     var store = Ext.create('Ext.data.Store', {
80906  *         autoLoad: true,
80907  *         model: 'User',
80908  *         data : data,
80909  *         proxy: {
80910  *             type: 'memory',
80911  *             reader: {
80912  *                 type: 'json',
80913  *                 root: 'users'
80914  *             }
80915  *         }
80916  *     });
80917  */
80918 Ext.define('Ext.data.proxy.Memory', {
80919     extend: 'Ext.data.proxy.Client',
80920     alias: 'proxy.memory',
80921     alternateClassName: 'Ext.data.MemoryProxy',
80922
80923     /**
80924      * @cfg {Ext.data.Model[]} data
80925      * Optional array of Records to load into the Proxy
80926      */
80927
80928     constructor: function(config) {
80929         this.callParent([config]);
80930
80931         //ensures that the reader has been instantiated properly
80932         this.setReader(this.reader);
80933     },
80934
80935     /**
80936      * Reads data from the configured {@link #data} object. Uses the Proxy's {@link #reader}, if present.
80937      * @param {Ext.data.Operation} operation The read Operation
80938      * @param {Function} callback The callback to call when reading has completed
80939      * @param {Object} scope The scope to call the callback function in
80940      */
80941     read: function(operation, callback, scope) {
80942         var me     = this,
80943             reader = me.getReader(),
80944             result = reader.read(me.data);
80945
80946         Ext.apply(operation, {
80947             resultSet: result
80948         });
80949
80950         operation.setCompleted();
80951         operation.setSuccessful();
80952         Ext.callback(callback, scope || me, [operation]);
80953     },
80954
80955     clear: Ext.emptyFn
80956 });
80957
80958 /**
80959  * @author Ed Spencer
80960  *
80961  * The Rest proxy is a specialization of the {@link Ext.data.proxy.Ajax AjaxProxy} which simply maps the four actions
80962  * (create, read, update and destroy) to RESTful HTTP verbs. For example, let's set up a {@link Ext.data.Model Model}
80963  * with an inline Rest proxy
80964  *
80965  *     Ext.define('User', {
80966  *         extend: 'Ext.data.Model',
80967  *         fields: ['id', 'name', 'email'],
80968  *
80969  *         proxy: {
80970  *             type: 'rest',
80971  *             url : '/users'
80972  *         }
80973  *     });
80974  *
80975  * 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
80976  * request to '/users':
80977  *
80978  *     var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
80979  *
80980  *     user.save(); //POST /users
80981  *
80982  * Let's expand this a little and provide a callback for the {@link Ext.data.Model#save} call to update the Model once
80983  * it has been created. We'll assume the creation went successfully and that the server gave this user an ID of 123:
80984  *
80985  *     user.save({
80986  *         success: function(user) {
80987  *             user.set('name', 'Khan Noonien Singh');
80988  *
80989  *             user.save(); //PUT /users/123
80990  *         }
80991  *     });
80992  *
80993  * Now that we're no longer creating a new Model instance, the request method is changed to an HTTP PUT, targeting the
80994  * relevant url for that user. Now let's delete this user, which will use the DELETE method:
80995  *
80996  *         user.destroy(); //DELETE /users/123
80997  *
80998  * Finally, when we perform a load of a Model or Store, Rest proxy will use the GET method:
80999  *
81000  *     //1. Load via Store
81001  *
81002  *     //the Store automatically picks up the Proxy from the User model
81003  *     var store = Ext.create('Ext.data.Store', {
81004  *         model: 'User'
81005  *     });
81006  *
81007  *     store.load(); //GET /users
81008  *
81009  *     //2. Load directly from the Model
81010  *
81011  *     //GET /users/123
81012  *     Ext.ModelManager.getModel('User').load(123, {
81013  *         success: function(user) {
81014  *             console.log(user.getId()); //outputs 123
81015  *         }
81016  *     });
81017  *
81018  * # Url generation
81019  *
81020  * The Rest proxy is able to automatically generate the urls above based on two configuration options - {@link #appendId} and
81021  * {@link #format}. If appendId is true (it is by default) then Rest proxy will automatically append the ID of the Model
81022  * instance in question to the configured url, resulting in the '/users/123' that we saw above.
81023  *
81024  * If the request is not for a specific Model instance (e.g. loading a Store), the url is not appended with an id.
81025  * The Rest proxy will automatically insert a '/' before the ID if one is not already present.
81026  *
81027  *     new Ext.data.proxy.Rest({
81028  *         url: '/users',
81029  *         appendId: true //default
81030  *     });
81031  *
81032  *     // Collection url: /users
81033  *     // Instance url  : /users/123
81034  *
81035  * The Rest proxy can also optionally append a format string to the end of any generated url:
81036  *
81037  *     new Ext.data.proxy.Rest({
81038  *         url: '/users',
81039  *         format: 'json'
81040  *     });
81041  *
81042  *     // Collection url: /users.json
81043  *     // Instance url  : /users/123.json
81044  *
81045  * If further customization is needed, simply implement the {@link #buildUrl} method and add your custom generated url
81046  * onto the {@link Ext.data.Request Request} object that is passed to buildUrl. See [Rest proxy's implementation][1] for
81047  * an example of how to achieve this.
81048  *
81049  * Note that Rest proxy inherits from {@link Ext.data.proxy.Ajax AjaxProxy}, which already injects all of the sorter,
81050  * filter, group and paging options into the generated url. See the {@link Ext.data.proxy.Ajax AjaxProxy docs} for more
81051  * details.
81052  *
81053  * [1]: source/RestProxy.html#method-Ext.data.proxy.Rest-buildUrl
81054  */
81055 Ext.define('Ext.data.proxy.Rest', {
81056     extend: 'Ext.data.proxy.Ajax',
81057     alternateClassName: 'Ext.data.RestProxy',
81058     alias : 'proxy.rest',
81059     
81060     /**
81061      * @cfg {Boolean} appendId
81062      * True to automatically append the ID of a Model instance when performing a request based on that single instance.
81063      * See Rest proxy intro docs for more details. Defaults to true.
81064      */
81065     appendId: true,
81066     
81067     /**
81068      * @cfg {String} format
81069      * Optional data format to send to the server when making any request (e.g. 'json'). See the Rest proxy intro docs
81070      * for full details. Defaults to undefined.
81071      */
81072     
81073     /**
81074      * @cfg {Boolean} batchActions
81075      * True to batch actions of a particular type when synchronizing the store. Defaults to false.
81076      */
81077     batchActions: false,
81078     
81079     /**
81080      * Specialized version of buildUrl that incorporates the {@link #appendId} and {@link #format} options into the
81081      * generated url. Override this to provide further customizations, but remember to call the superclass buildUrl so
81082      * that additional parameters like the cache buster string are appended.
81083      * @param {Object} request
81084      */
81085     buildUrl: function(request) {
81086         var me        = this,
81087             operation = request.operation,
81088             records   = operation.records || [],
81089             record    = records[0],
81090             format    = me.format,
81091             url       = me.getUrl(request),
81092             id        = record ? record.getId() : operation.id;
81093         
81094         if (me.appendId && id) {
81095             if (!url.match(/\/$/)) {
81096                 url += '/';
81097             }
81098             
81099             url += id;
81100         }
81101         
81102         if (format) {
81103             if (!url.match(/\.$/)) {
81104                 url += '.';
81105             }
81106             
81107             url += format;
81108         }
81109         
81110         request.url = url;
81111         
81112         return me.callParent(arguments);
81113     }
81114 }, function() {
81115     Ext.apply(this.prototype, {
81116         /**
81117          * @property {Object} actionMethods
81118          * Mapping of action name to HTTP request method. These default to RESTful conventions for the 'create', 'read',
81119          * 'update' and 'destroy' actions (which map to 'POST', 'GET', 'PUT' and 'DELETE' respectively). This object
81120          * should not be changed except globally via {@link Ext#override Ext.override} - the {@link #getMethod} function
81121          * can be overridden instead.
81122          */
81123         actionMethods: {
81124             create : 'POST',
81125             read   : 'GET',
81126             update : 'PUT',
81127             destroy: 'DELETE'
81128         }
81129     });
81130 });
81131
81132 /**
81133  * @author Ed Spencer
81134  *
81135  * Proxy which uses HTML5 session storage as its data storage/retrieval mechanism. If this proxy is used in a browser
81136  * where session storage is not supported, the constructor will throw an error. A session storage proxy requires a
81137  * unique ID which is used as a key in which all record data are stored in the session storage object.
81138  *
81139  * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
81140  * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
81141  *
81142  * Proxies are almost always used with a {@link Ext.data.Store store}:
81143  *
81144  *     new Ext.data.Store({
81145  *         proxy: {
81146  *             type: 'sessionstorage',
81147  *             id  : 'myProxyKey'
81148  *         }
81149  *     });
81150  *
81151  * Alternatively you can instantiate the Proxy directly:
81152  *
81153  *     new Ext.data.proxy.SessionStorage({
81154  *         id  : 'myOtherProxyKey'
81155  *     });
81156  *
81157  * Note that session storage is different to local storage (see {@link Ext.data.proxy.LocalStorage}) - if a browser
81158  * session is ended (e.g. by closing the browser) then all data in a SessionStorageProxy are lost. Browser restarts
81159  * don't affect the {@link Ext.data.proxy.LocalStorage} - the data are preserved.
81160  */
81161 Ext.define('Ext.data.proxy.SessionStorage', {
81162     extend: 'Ext.data.proxy.WebStorage',
81163     alias: 'proxy.sessionstorage',
81164     alternateClassName: 'Ext.data.SessionStorageProxy',
81165     
81166     //inherit docs
81167     getStorageObject: function() {
81168         return window.sessionStorage;
81169     }
81170 });
81171
81172 /**
81173  * @author Ed Spencer
81174  * @class Ext.data.reader.Array
81175  * @extends Ext.data.reader.Json
81176  * 
81177  * <p>Data reader class to create an Array of {@link Ext.data.Model} objects from an Array.
81178  * Each element of that Array represents a row of data fields. The
81179  * fields are pulled into a Record object using as a subscript, the <code>mapping</code> property
81180  * of the field definition if it exists, or the field's ordinal position in the definition.</p>
81181  * 
81182  * <p><u>Example code:</u></p>
81183  * 
81184 <pre><code>
81185 Employee = Ext.define('Employee', {
81186     extend: 'Ext.data.Model',
81187     fields: [
81188         'id',
81189         {name: 'name', mapping: 1},         // "mapping" only needed if an "id" field is present which
81190         {name: 'occupation', mapping: 2}    // precludes using the ordinal position as the index.        
81191     ]
81192 });
81193
81194 var myReader = new Ext.data.reader.Array({
81195     model: 'Employee'
81196 }, Employee);
81197 </code></pre>
81198  * 
81199  * <p>This would consume an Array like this:</p>
81200  * 
81201 <pre><code>
81202 [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
81203 </code></pre>
81204  * 
81205  * @constructor
81206  * Create a new ArrayReader
81207  * @param {Object} meta Metadata configuration options.
81208  */
81209 Ext.define('Ext.data.reader.Array', {
81210     extend: 'Ext.data.reader.Json',
81211     alternateClassName: 'Ext.data.ArrayReader',
81212     alias : 'reader.array',
81213
81214     /**
81215      * @private
81216      * Most of the work is done for us by JsonReader, but we need to overwrite the field accessors to just
81217      * reference the correct position in the array.
81218      */
81219     buildExtractors: function() {
81220         this.callParent(arguments);
81221         
81222         var fields = this.model.prototype.fields.items,
81223             i = 0,
81224             length = fields.length,
81225             extractorFunctions = [],
81226             map;
81227         
81228         for (; i < length; i++) {
81229             map = fields[i].mapping;
81230             extractorFunctions.push(function(index) {
81231                 return function(data) {
81232                     return data[index];
81233                 };
81234             }(map !== null ? map : i));
81235         }
81236         
81237         this.extractorFunctions = extractorFunctions;
81238     }
81239 });
81240
81241 /**
81242  * @author Ed Spencer
81243  * @class Ext.data.reader.Xml
81244  * @extends Ext.data.reader.Reader
81245  *
81246  * <p>The XML Reader is used by a Proxy to read a server response that is sent back in XML format. This usually
81247  * happens as a result of loading a Store - for example we might create something like this:</p>
81248  *
81249 <pre><code>
81250 Ext.define('User', {
81251     extend: 'Ext.data.Model',
81252     fields: ['id', 'name', 'email']
81253 });
81254
81255 var store = Ext.create('Ext.data.Store', {
81256     model: 'User',
81257     proxy: {
81258         type: 'ajax',
81259         url : 'users.xml',
81260         reader: {
81261             type: 'xml',
81262             record: 'user'
81263         }
81264     }
81265 });
81266 </code></pre>
81267  *
81268  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
81269  * not already familiar with them.</p>
81270  *
81271  * <p>We created the simplest type of XML Reader possible by simply telling our {@link Ext.data.Store Store}'s
81272  * {@link Ext.data.proxy.Proxy Proxy} that we want a XML Reader. The Store automatically passes the configured model to the
81273  * Store, so it is as if we passed this instead:
81274  *
81275 <pre><code>
81276 reader: {
81277     type : 'xml',
81278     model: 'User',
81279     record: 'user'
81280 }
81281 </code></pre>
81282  *
81283  * <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>
81284  *
81285 <pre><code>
81286 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
81287 &lt;user&gt;
81288     &lt;id&gt;1&lt;/id&gt;
81289     &lt;name&gt;Ed Spencer&lt;/name&gt;
81290     &lt;email&gt;ed@sencha.com&lt;/email&gt;
81291 &lt;/user&gt;
81292 &lt;user&gt;
81293     &lt;id&gt;2&lt;/id&gt;
81294     &lt;name&gt;Abe Elias&lt;/name&gt;
81295     &lt;email&gt;abe@sencha.com&lt;/email&gt;
81296 &lt;/user&gt;
81297 </code></pre>
81298  *
81299  * <p>The XML Reader uses the configured {@link #record} option to pull out the data for each record - in this case we
81300  * set record to 'user', so each &lt;user&gt; above will be converted into a User model.</p>
81301  *
81302  * <p><u>Reading other XML formats</u></p>
81303  *
81304  * <p>If you already have your XML format defined and it doesn't look quite like what we have above, you can usually
81305  * pass XmlReader a couple of configuration options to make it parse your format. For example, we can use the
81306  * {@link #root} configuration to parse data that comes back like this:</p>
81307  *
81308 <pre><code>
81309 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
81310 &lt;users&gt;
81311     &lt;user&gt;
81312         &lt;id&gt;1&lt;/id&gt;
81313         &lt;name&gt;Ed Spencer&lt;/name&gt;
81314         &lt;email&gt;ed@sencha.com&lt;/email&gt;
81315     &lt;/user&gt;
81316     &lt;user&gt;
81317         &lt;id&gt;2&lt;/id&gt;
81318         &lt;name&gt;Abe Elias&lt;/name&gt;
81319         &lt;email&gt;abe@sencha.com&lt;/email&gt;
81320     &lt;/user&gt;
81321 &lt;/users&gt;
81322 </code></pre>
81323  *
81324  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
81325  *
81326 <pre><code>
81327 reader: {
81328     type  : 'xml',
81329     root  : 'users',
81330     record: 'user'
81331 }
81332 </code></pre>
81333  *
81334  * <p>Note that XmlReader doesn't care whether your {@link #root} and {@link #record} elements are nested deep inside
81335  * a larger structure, so a response like this will still work:
81336  *
81337 <pre><code>
81338 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
81339 &lt;deeply&gt;
81340     &lt;nested&gt;
81341         &lt;xml&gt;
81342             &lt;users&gt;
81343                 &lt;user&gt;
81344                     &lt;id&gt;1&lt;/id&gt;
81345                     &lt;name&gt;Ed Spencer&lt;/name&gt;
81346                     &lt;email&gt;ed@sencha.com&lt;/email&gt;
81347                 &lt;/user&gt;
81348                 &lt;user&gt;
81349                     &lt;id&gt;2&lt;/id&gt;
81350                     &lt;name&gt;Abe Elias&lt;/name&gt;
81351                     &lt;email&gt;abe@sencha.com&lt;/email&gt;
81352                 &lt;/user&gt;
81353             &lt;/users&gt;
81354         &lt;/xml&gt;
81355     &lt;/nested&gt;
81356 &lt;/deeply&gt;
81357 </code></pre>
81358  *
81359  * <p><u>Response metadata</u></p>
81360  *
81361  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
81362  * and the {@link #successProperty success status of the response}. These are typically included in the XML response
81363  * like this:</p>
81364  *
81365 <pre><code>
81366 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
81367 &lt;total&gt;100&lt;/total&gt;
81368 &lt;success&gt;true&lt;/success&gt;
81369 &lt;users&gt;
81370     &lt;user&gt;
81371         &lt;id&gt;1&lt;/id&gt;
81372         &lt;name&gt;Ed Spencer&lt;/name&gt;
81373         &lt;email&gt;ed@sencha.com&lt;/email&gt;
81374     &lt;/user&gt;
81375     &lt;user&gt;
81376         &lt;id&gt;2&lt;/id&gt;
81377         &lt;name&gt;Abe Elias&lt;/name&gt;
81378         &lt;email&gt;abe@sencha.com&lt;/email&gt;
81379     &lt;/user&gt;
81380 &lt;/users&gt;
81381 </code></pre>
81382  *
81383  * <p>If these properties are present in the XML response they can be parsed out by the XmlReader and used by the
81384  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
81385  * options:</p>
81386  *
81387 <pre><code>
81388 reader: {
81389     type: 'xml',
81390     root: 'users',
81391     totalProperty  : 'total',
81392     successProperty: 'success'
81393 }
81394 </code></pre>
81395  *
81396  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
81397  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
81398  * returned.</p>
81399  *
81400  * <p><u>Response format</u></p>
81401  *
81402  * <p><b>Note:</b> in order for the browser to parse a returned XML document, the Content-Type header in the HTTP
81403  * response must be set to "text/xml" or "application/xml". This is very important - the XmlReader will not
81404  * work correctly otherwise.</p>
81405  */
81406 Ext.define('Ext.data.reader.Xml', {
81407     extend: 'Ext.data.reader.Reader',
81408     alternateClassName: 'Ext.data.XmlReader',
81409     alias : 'reader.xml',
81410
81411     /**
81412      * @cfg {String} record (required)
81413      * The DomQuery path to the repeated element which contains record information.
81414      */
81415
81416     /**
81417      * @private
81418      * Creates a function to return some particular key of data from a response. The totalProperty and
81419      * successProperty are treated as special cases for type casting, everything else is just a simple selector.
81420      * @param {String} key
81421      * @return {Function}
81422      */
81423     createAccessor: function(expr) {
81424         var me = this;
81425
81426         if (Ext.isEmpty(expr)) {
81427             return Ext.emptyFn;
81428         }
81429
81430         if (Ext.isFunction(expr)) {
81431             return expr;
81432         }
81433
81434         return function(root) {
81435             return me.getNodeValue(Ext.DomQuery.selectNode(expr, root));
81436         };
81437     },
81438
81439     getNodeValue: function(node) {
81440         if (node && node.firstChild) {
81441             return node.firstChild.nodeValue;
81442         }
81443         return undefined;
81444     },
81445
81446     //inherit docs
81447     getResponseData: function(response) {
81448         var xml = response.responseXML;
81449
81450
81451         return xml;
81452     },
81453
81454     /**
81455      * Normalizes the data object
81456      * @param {Object} data The raw data object
81457      * @return {Object} Returns the documentElement property of the data object if present, or the same object if not
81458      */
81459     getData: function(data) {
81460         return data.documentElement || data;
81461     },
81462
81463     /**
81464      * @private
81465      * Given an XML object, returns the Element that represents the root as configured by the Reader's meta data
81466      * @param {Object} data The XML data object
81467      * @return {XMLElement} The root node element
81468      */
81469     getRoot: function(data) {
81470         var nodeName = data.nodeName,
81471             root     = this.root;
81472
81473         if (!root || (nodeName && nodeName == root)) {
81474             return data;
81475         } else if (Ext.DomQuery.isXml(data)) {
81476             // This fix ensures we have XML data
81477             // Related to TreeStore calling getRoot with the root node, which isn't XML
81478             // Probably should be resolved in TreeStore at some point
81479             return Ext.DomQuery.selectNode(root, data);
81480         }
81481     },
81482
81483     /**
81484      * @private
81485      * We're just preparing the data for the superclass by pulling out the record nodes we want
81486      * @param {XMLElement} root The XML root node
81487      * @return {Ext.data.Model[]} The records
81488      */
81489     extractData: function(root) {
81490         var recordName = this.record;
81491
81492
81493         if (recordName != root.nodeName) {
81494             root = Ext.DomQuery.select(recordName, root);
81495         } else {
81496             root = [root];
81497         }
81498         return this.callParent([root]);
81499     },
81500
81501     /**
81502      * @private
81503      * See Ext.data.reader.Reader's getAssociatedDataRoot docs
81504      * @param {Object} data The raw data object
81505      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
81506      * @return {XMLElement} The root
81507      */
81508     getAssociatedDataRoot: function(data, associationName) {
81509         return Ext.DomQuery.select(associationName, data)[0];
81510     },
81511
81512     /**
81513      * Parses an XML document and returns a ResultSet containing the model instances
81514      * @param {Object} doc Parsed XML document
81515      * @return {Ext.data.ResultSet} The parsed result set
81516      */
81517     readRecords: function(doc) {
81518         //it's possible that we get passed an array here by associations. Make sure we strip that out (see Ext.data.reader.Reader#readAssociated)
81519         if (Ext.isArray(doc)) {
81520             doc = doc[0];
81521         }
81522
81523         /**
81524          * @deprecated will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
81525          * @property xmlData
81526          * @type Object
81527          */
81528         this.xmlData = doc;
81529         return this.callParent([doc]);
81530     }
81531 });
81532 /**
81533  * @author Ed Spencer
81534  * @class Ext.data.writer.Xml
81535  * @extends Ext.data.writer.Writer
81536
81537 This class is used to write {@link Ext.data.Model} data to the server in an XML format.
81538 The {@link #documentRoot} property is used to specify the root element in the XML document.
81539 The {@link #record} option is used to specify the element name for each record that will make
81540 up the XML document.
81541
81542  * @markdown
81543  */
81544 Ext.define('Ext.data.writer.Xml', {
81545     
81546     /* Begin Definitions */
81547     
81548     extend: 'Ext.data.writer.Writer',
81549     alternateClassName: 'Ext.data.XmlWriter',
81550     
81551     alias: 'writer.xml',
81552     
81553     /* End Definitions */
81554     
81555     /**
81556      * @cfg {String} documentRoot The name of the root element of the document. Defaults to <tt>'xmlData'</tt>.
81557      * If there is more than 1 record and the root is not specified, the default document root will still be used
81558      * to ensure a valid XML document is created.
81559      */
81560     documentRoot: 'xmlData',
81561     
81562     /**
81563      * @cfg {String} defaultDocumentRoot The root to be used if {@link #documentRoot} is empty and a root is required
81564      * to form a valid XML document.
81565      */
81566     defaultDocumentRoot: 'xmlData',
81567
81568     /**
81569      * @cfg {String} header A header to use in the XML document (such as setting the encoding or version).
81570      * Defaults to <tt>''</tt>.
81571      */
81572     header: '',
81573
81574     /**
81575      * @cfg {String} record The name of the node to use for each record. Defaults to <tt>'record'</tt>.
81576      */
81577     record: 'record',
81578
81579     //inherit docs
81580     writeRecords: function(request, data) {
81581         var me = this,
81582             xml = [],
81583             i = 0,
81584             len = data.length,
81585             root = me.documentRoot,
81586             record = me.record,
81587             needsRoot = data.length !== 1,
81588             item,
81589             key;
81590             
81591         // may not exist
81592         xml.push(me.header || '');
81593         
81594         if (!root && needsRoot) {
81595             root = me.defaultDocumentRoot;
81596         }
81597         
81598         if (root) {
81599             xml.push('<', root, '>');
81600         }
81601             
81602         for (; i < len; ++i) {
81603             item = data[i];
81604             xml.push('<', record, '>');
81605             for (key in item) {
81606                 if (item.hasOwnProperty(key)) {
81607                     xml.push('<', key, '>', item[key], '</', key, '>');
81608                 }
81609             }
81610             xml.push('</', record, '>');
81611         }
81612         
81613         if (root) {
81614             xml.push('</', root, '>');
81615         }
81616             
81617         request.xmlData = xml.join('');
81618         return request;
81619     }
81620 });
81621
81622 /**
81623  * @class Ext.direct.Event
81624  * A base class for all Ext.direct events. An event is
81625  * created after some kind of interaction with the server.
81626  * The event class is essentially just a data structure
81627  * to hold a Direct response.
81628  */
81629 Ext.define('Ext.direct.Event', {
81630
81631     /* Begin Definitions */
81632
81633     alias: 'direct.event',
81634
81635     requires: ['Ext.direct.Manager'],
81636
81637     /* End Definitions */
81638
81639     status: true,
81640
81641     /**
81642      * Creates new Event.
81643      * @param {Object} config (optional) Config object.
81644      */
81645     constructor: function(config) {
81646         Ext.apply(this, config);
81647     },
81648
81649     /**
81650      * Return the raw data for this event.
81651      * @return {Object} The data from the event
81652      */
81653     getData: function(){
81654         return this.data;
81655     }
81656 });
81657
81658 /**
81659  * @class Ext.direct.RemotingEvent
81660  * @extends Ext.direct.Event
81661  * An event that is fired when data is received from a 
81662  * {@link Ext.direct.RemotingProvider}. Contains a method to the
81663  * related transaction for the direct request, see {@link #getTransaction}
81664  */
81665 Ext.define('Ext.direct.RemotingEvent', {
81666     
81667     /* Begin Definitions */
81668    
81669     extend: 'Ext.direct.Event',
81670     
81671     alias: 'direct.rpc',
81672     
81673     /* End Definitions */
81674     
81675     /**
81676      * Get the transaction associated with this event.
81677      * @return {Ext.direct.Transaction} The transaction
81678      */
81679     getTransaction: function(){
81680         return this.transaction || Ext.direct.Manager.getTransaction(this.tid);
81681     }
81682 });
81683
81684 /**
81685  * @class Ext.direct.ExceptionEvent
81686  * @extends Ext.direct.RemotingEvent
81687  * An event that is fired when an exception is received from a {@link Ext.direct.RemotingProvider}
81688  */
81689 Ext.define('Ext.direct.ExceptionEvent', {
81690     
81691     /* Begin Definitions */
81692    
81693     extend: 'Ext.direct.RemotingEvent',
81694     
81695     alias: 'direct.exception',
81696     
81697     /* End Definitions */
81698    
81699    status: false
81700 });
81701
81702 /**
81703  * @class Ext.direct.Provider
81704  * <p>Ext.direct.Provider is an abstract class meant to be extended.</p>
81705  *
81706  * <p>For example Ext JS implements the following subclasses:</p>
81707  * <pre><code>
81708 Provider
81709 |
81710 +---{@link Ext.direct.JsonProvider JsonProvider}
81711     |
81712     +---{@link Ext.direct.PollingProvider PollingProvider}
81713     |
81714     +---{@link Ext.direct.RemotingProvider RemotingProvider}
81715  * </code></pre>
81716  * @abstract
81717  */
81718 Ext.define('Ext.direct.Provider', {
81719
81720     /* Begin Definitions */
81721
81722    alias: 'direct.provider',
81723
81724     mixins: {
81725         observable: 'Ext.util.Observable'
81726     },
81727
81728     /* End Definitions */
81729
81730    /**
81731      * @cfg {String} id
81732      * The unique id of the provider (defaults to an {@link Ext#id auto-assigned id}).
81733      * You should assign an id if you need to be able to access the provider later and you do
81734      * not have an object reference available, for example:
81735      * <pre><code>
81736 Ext.direct.Manager.addProvider({
81737     type: 'polling',
81738     url:  'php/poll.php',
81739     id:   'poll-provider'
81740 });
81741 var p = {@link Ext.direct.Manager}.{@link Ext.direct.Manager#getProvider getProvider}('poll-provider');
81742 p.disconnect();
81743      * </code></pre>
81744      */
81745
81746     constructor : function(config){
81747         var me = this;
81748
81749         Ext.apply(me, config);
81750         me.addEvents(
81751             /**
81752              * @event connect
81753              * Fires when the Provider connects to the server-side
81754              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
81755              */
81756             'connect',
81757             /**
81758              * @event disconnect
81759              * Fires when the Provider disconnects from the server-side
81760              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
81761              */
81762             'disconnect',
81763             /**
81764              * @event data
81765              * Fires when the Provider receives data from the server-side
81766              * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
81767              * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
81768              */
81769             'data',
81770             /**
81771              * @event exception
81772              * Fires when the Provider receives an exception from the server-side
81773              */
81774             'exception'
81775         );
81776         me.mixins.observable.constructor.call(me, config);
81777     },
81778
81779     /**
81780      * Returns whether or not the server-side is currently connected.
81781      * Abstract method for subclasses to implement.
81782      */
81783     isConnected: function(){
81784         return false;
81785     },
81786
81787     /**
81788      * Abstract methods for subclasses to implement.
81789      * @method
81790      */
81791     connect: Ext.emptyFn,
81792
81793     /**
81794      * Abstract methods for subclasses to implement.
81795      * @method
81796      */
81797     disconnect: Ext.emptyFn
81798 });
81799
81800 /**
81801  * @class Ext.direct.JsonProvider
81802  * @extends Ext.direct.Provider
81803
81804 A base provider for communicating using JSON. This is an abstract class
81805 and should not be instanced directly.
81806
81807  * @markdown
81808  * @abstract
81809  */
81810
81811 Ext.define('Ext.direct.JsonProvider', {
81812
81813     /* Begin Definitions */
81814
81815     extend: 'Ext.direct.Provider',
81816
81817     alias: 'direct.jsonprovider',
81818
81819     uses: ['Ext.direct.ExceptionEvent'],
81820
81821     /* End Definitions */
81822
81823    /**
81824     * Parse the JSON response
81825     * @private
81826     * @param {Object} response The XHR response object
81827     * @return {Object} The data in the response.
81828     */
81829    parseResponse: function(response){
81830         if (!Ext.isEmpty(response.responseText)) {
81831             if (Ext.isObject(response.responseText)) {
81832                 return response.responseText;
81833             }
81834             return Ext.decode(response.responseText);
81835         }
81836         return null;
81837     },
81838
81839     /**
81840      * Creates a set of events based on the XHR response
81841      * @private
81842      * @param {Object} response The XHR response
81843      * @return {Ext.direct.Event[]} An array of Ext.direct.Event
81844      */
81845     createEvents: function(response){
81846         var data = null,
81847             events = [],
81848             event,
81849             i = 0,
81850             len;
81851
81852         try{
81853             data = this.parseResponse(response);
81854         } catch(e) {
81855             event = Ext.create('Ext.direct.ExceptionEvent', {
81856                 data: e,
81857                 xhr: response,
81858                 code: Ext.direct.Manager.self.exceptions.PARSE,
81859                 message: 'Error parsing json response: \n\n ' + data
81860             });
81861             return [event];
81862         }
81863
81864         if (Ext.isArray(data)) {
81865             for (len = data.length; i < len; ++i) {
81866                 events.push(this.createEvent(data[i]));
81867             }
81868         } else {
81869             events.push(this.createEvent(data));
81870         }
81871         return events;
81872     },
81873
81874     /**
81875      * Create an event from a response object
81876      * @param {Object} response The XHR response object
81877      * @return {Ext.direct.Event} The event
81878      */
81879     createEvent: function(response){
81880         return Ext.create('direct.' + response.type, response);
81881     }
81882 });
81883 /**
81884  * @class Ext.direct.PollingProvider
81885  * @extends Ext.direct.JsonProvider
81886  *
81887  * <p>Provides for repetitive polling of the server at distinct {@link #interval intervals}.
81888  * The initial request for data originates from the client, and then is responded to by the
81889  * server.</p>
81890  * 
81891  * <p>All configurations for the PollingProvider should be generated by the server-side
81892  * API portion of the Ext.Direct stack.</p>
81893  *
81894  * <p>An instance of PollingProvider may be created directly via the new keyword or by simply
81895  * specifying <tt>type = 'polling'</tt>.  For example:</p>
81896  * <pre><code>
81897 var pollA = new Ext.direct.PollingProvider({
81898     type:'polling',
81899     url: 'php/pollA.php',
81900 });
81901 Ext.direct.Manager.addProvider(pollA);
81902 pollA.disconnect();
81903
81904 Ext.direct.Manager.addProvider(
81905     {
81906         type:'polling',
81907         url: 'php/pollB.php',
81908         id: 'pollB-provider'
81909     }
81910 );
81911 var pollB = Ext.direct.Manager.getProvider('pollB-provider');
81912  * </code></pre>
81913  */
81914 Ext.define('Ext.direct.PollingProvider', {
81915     
81916     /* Begin Definitions */
81917     
81918     extend: 'Ext.direct.JsonProvider',
81919     
81920     alias: 'direct.pollingprovider',
81921     
81922     uses: ['Ext.direct.ExceptionEvent'],
81923     
81924     requires: ['Ext.Ajax', 'Ext.util.DelayedTask'],
81925     
81926     /* End Definitions */
81927     
81928     /**
81929      * @cfg {Number} interval
81930      * How often to poll the server-side in milliseconds. Defaults to every 3 seconds.
81931      */
81932     interval: 3000,
81933
81934     /**
81935      * @cfg {Object} baseParams
81936      * An object containing properties which are to be sent as parameters on every polling request
81937      */
81938     
81939     /**
81940      * @cfg {String/Function} url
81941      * The url which the PollingProvider should contact with each request. This can also be
81942      * an imported Ext.Direct method which will accept the baseParams as its only argument.
81943      */
81944
81945     // private
81946     constructor : function(config){
81947         this.callParent(arguments);
81948         this.addEvents(
81949             /**
81950              * @event beforepoll
81951              * Fired immediately before a poll takes place, an event handler can return false
81952              * in order to cancel the poll.
81953              * @param {Ext.direct.PollingProvider} this
81954              */
81955             'beforepoll',            
81956             /**
81957              * @event poll
81958              * This event has not yet been implemented.
81959              * @param {Ext.direct.PollingProvider} this
81960              */
81961             'poll'
81962         );
81963     },
81964
81965     // inherited
81966     isConnected: function(){
81967         return !!this.pollTask;
81968     },
81969
81970     /**
81971      * Connect to the server-side and begin the polling process. To handle each
81972      * response subscribe to the data event.
81973      */
81974     connect: function(){
81975         var me = this, url = me.url;
81976         
81977         if (url && !me.pollTask) {
81978             me.pollTask = Ext.TaskManager.start({
81979                 run: function(){
81980                     if (me.fireEvent('beforepoll', me) !== false) {
81981                         if (Ext.isFunction(url)) {
81982                             url(me.baseParams);
81983                         } else {
81984                             Ext.Ajax.request({
81985                                 url: url,
81986                                 callback: me.onData,
81987                                 scope: me,
81988                                 params: me.baseParams
81989                             });
81990                         }
81991                     }
81992                 },
81993                 interval: me.interval,
81994                 scope: me
81995             });
81996             me.fireEvent('connect', me);
81997         } else if (!url) {
81998         }
81999     },
82000
82001     /**
82002      * Disconnect from the server-side and stop the polling process. The disconnect
82003      * event will be fired on a successful disconnect.
82004      */
82005     disconnect: function(){
82006         var me = this;
82007         
82008         if (me.pollTask) {
82009             Ext.TaskManager.stop(me.pollTask);
82010             delete me.pollTask;
82011             me.fireEvent('disconnect', me);
82012         }
82013     },
82014
82015     // private
82016     onData: function(opt, success, response){
82017         var me = this, 
82018             i = 0, 
82019             len,
82020             events;
82021         
82022         if (success) {
82023             events = me.createEvents(response);
82024             for (len = events.length; i < len; ++i) {
82025                 me.fireEvent('data', me, events[i]);
82026             }
82027         } else {
82028             me.fireEvent('data', me, Ext.create('Ext.direct.ExceptionEvent', {
82029                 data: null,
82030                 code: Ext.direct.Manager.self.exceptions.TRANSPORT,
82031                 message: 'Unable to connect to the server.',
82032                 xhr: response
82033             }));
82034         }
82035     }
82036 });
82037 /**
82038  * Small utility class used internally to represent a Direct method.
82039  * @class Ext.direct.RemotingMethod
82040  * @ignore
82041  */
82042 Ext.define('Ext.direct.RemotingMethod', {
82043
82044     constructor: function(config){
82045         var me = this,
82046             params = Ext.isDefined(config.params) ? config.params : config.len,
82047             name;
82048
82049         me.name = config.name;
82050         me.formHandler = config.formHandler;
82051         if (Ext.isNumber(params)) {
82052             // given only the number of parameters
82053             me.len = params;
82054             me.ordered = true;
82055         } else {
82056             /*
82057              * Given an array of either
82058              * a) String
82059              * b) Objects with a name property. We may want to encode extra info in here later
82060              */
82061             me.params = [];
82062             Ext.each(params, function(param){
82063                 name = Ext.isObject(param) ? param.name : param;
82064                 me.params.push(name);
82065             });
82066         }
82067     },
82068
82069     /**
82070      * Takes the arguments for the Direct function and splits the arguments
82071      * from the scope and the callback.
82072      * @param {Array} args The arguments passed to the direct call
82073      * @return {Object} An object with 3 properties, args, callback & scope.
82074      */
82075     getCallData: function(args){
82076         var me = this,
82077             data = null,
82078             len  = me.len,
82079             params = me.params,
82080             callback,
82081             scope,
82082             name;
82083
82084         if (me.ordered) {
82085             callback = args[len];
82086             scope = args[len + 1];
82087             if (len !== 0) {
82088                 data = args.slice(0, len);
82089             }
82090         } else {
82091             data = Ext.apply({}, args[0]);
82092             callback = args[1];
82093             scope = args[2];
82094
82095             // filter out any non-existent properties
82096             for (name in data) {
82097                 if (data.hasOwnProperty(name)) {
82098                     if (!Ext.Array.contains(params, name)) {
82099                         delete data[name];
82100                     }
82101                 }
82102             }
82103         }
82104
82105         return {
82106             data: data,
82107             callback: callback,
82108             scope: scope
82109         };
82110     }
82111 });
82112
82113 /**
82114  * Supporting Class for Ext.Direct (not intended to be used directly).
82115  */
82116 Ext.define('Ext.direct.Transaction', {
82117     
82118     /* Begin Definitions */
82119    
82120     alias: 'direct.transaction',
82121     alternateClassName: 'Ext.Direct.Transaction',
82122    
82123     statics: {
82124         TRANSACTION_ID: 0
82125     },
82126    
82127     /* End Definitions */
82128
82129     /**
82130      * Creates new Transaction.
82131      * @param {Object} [config] Config object.
82132      */
82133     constructor: function(config){
82134         var me = this;
82135         
82136         Ext.apply(me, config);
82137         me.id = ++me.self.TRANSACTION_ID;
82138         me.retryCount = 0;
82139     },
82140    
82141     send: function(){
82142          this.provider.queueTransaction(this);
82143     },
82144
82145     retry: function(){
82146         this.retryCount++;
82147         this.send();
82148     },
82149
82150     getProvider: function(){
82151         return this.provider;
82152     }
82153 });
82154
82155 /**
82156  * @class Ext.direct.RemotingProvider
82157  * @extends Ext.direct.JsonProvider
82158  * 
82159  * <p>The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
82160  * server side methods on the client (a remote procedure call (RPC) type of
82161  * connection where the client can initiate a procedure on the server).</p>
82162  * 
82163  * <p>This allows for code to be organized in a fashion that is maintainable,
82164  * while providing a clear path between client and server, something that is
82165  * not always apparent when using URLs.</p>
82166  * 
82167  * <p>To accomplish this the server-side needs to describe what classes and methods
82168  * are available on the client-side. This configuration will typically be
82169  * outputted by the server-side Ext.Direct stack when the API description is built.</p>
82170  */
82171 Ext.define('Ext.direct.RemotingProvider', {
82172     
82173     /* Begin Definitions */
82174    
82175     alias: 'direct.remotingprovider',
82176     
82177     extend: 'Ext.direct.JsonProvider', 
82178     
82179     requires: [
82180         'Ext.util.MixedCollection', 
82181         'Ext.util.DelayedTask', 
82182         'Ext.direct.Transaction',
82183         'Ext.direct.RemotingMethod'
82184     ],
82185    
82186     /* End Definitions */
82187    
82188    /**
82189      * @cfg {Object} actions
82190      * Object literal defining the server side actions and methods. For example, if
82191      * the Provider is configured with:
82192      * <pre><code>
82193 "actions":{ // each property within the 'actions' object represents a server side Class 
82194     "TestAction":[ // array of methods within each server side Class to be   
82195     {              // stubbed out on client
82196         "name":"doEcho", 
82197         "len":1            
82198     },{
82199         "name":"multiply",// name of method
82200         "len":2           // The number of parameters that will be used to create an
82201                           // array of data to send to the server side function.
82202                           // Ensure the server sends back a Number, not a String. 
82203     },{
82204         "name":"doForm",
82205         "formHandler":true, // direct the client to use specialized form handling method 
82206         "len":1
82207     }]
82208 }
82209      * </code></pre>
82210      * <p>Note that a Store is not required, a server method can be called at any time.
82211      * In the following example a <b>client side</b> handler is used to call the
82212      * server side method "multiply" in the server-side "TestAction" Class:</p>
82213      * <pre><code>
82214 TestAction.multiply(
82215     2, 4, // pass two arguments to server, so specify len=2
82216     // callback function after the server is called
82217     // result: the result returned by the server
82218     //      e: Ext.direct.RemotingEvent object
82219     function(result, e){
82220         var t = e.getTransaction();
82221         var action = t.action; // server side Class called
82222         var method = t.method; // server side method called
82223         if(e.status){
82224             var answer = Ext.encode(result); // 8
82225     
82226         }else{
82227             var msg = e.message; // failure message
82228         }
82229     }
82230 );
82231      * </code></pre>
82232      * In the example above, the server side "multiply" function will be passed two
82233      * arguments (2 and 4).  The "multiply" method should return the value 8 which will be
82234      * available as the <tt>result</tt> in the example above. 
82235      */
82236     
82237     /**
82238      * @cfg {String/Object} namespace
82239      * Namespace for the Remoting Provider (defaults to the browser global scope of <i>window</i>).
82240      * Explicitly specify the namespace Object, or specify a String to have a
82241      * {@link Ext#namespace namespace created} implicitly.
82242      */
82243     
82244     /**
82245      * @cfg {String} url
82246      * <b>Required</b>. The url to connect to the {@link Ext.direct.Manager} server-side router. 
82247      */
82248     
82249     /**
82250      * @cfg {String} enableUrlEncode
82251      * Specify which param will hold the arguments for the method.
82252      * Defaults to <tt>'data'</tt>.
82253      */
82254     
82255     /**
82256      * @cfg {Number/Boolean} enableBuffer
82257      * <p><tt>true</tt> or <tt>false</tt> to enable or disable combining of method
82258      * calls. If a number is specified this is the amount of time in milliseconds
82259      * to wait before sending a batched request.</p>
82260      * <br><p>Calls which are received within the specified timeframe will be
82261      * concatenated together and sent in a single request, optimizing the
82262      * application by reducing the amount of round trips that have to be made
82263      * to the server.</p>
82264      */
82265     enableBuffer: 10,
82266     
82267     /**
82268      * @cfg {Number} maxRetries
82269      * Number of times to re-attempt delivery on failure of a call.
82270      */
82271     maxRetries: 1,
82272     
82273     /**
82274      * @cfg {Number} timeout
82275      * The timeout to use for each request.
82276      */
82277     timeout: undefined,
82278     
82279     constructor : function(config){
82280         var me = this;
82281         me.callParent(arguments);
82282         me.addEvents(
82283             /**
82284              * @event beforecall
82285              * Fires immediately before the client-side sends off the RPC call.
82286              * By returning false from an event handler you can prevent the call from
82287              * executing.
82288              * @param {Ext.direct.RemotingProvider} provider
82289              * @param {Ext.direct.Transaction} transaction
82290              * @param {Object} meta The meta data
82291              */            
82292             'beforecall',            
82293             /**
82294              * @event call
82295              * Fires immediately after the request to the server-side is sent. This does
82296              * NOT fire after the response has come back from the call.
82297              * @param {Ext.direct.RemotingProvider} provider
82298              * @param {Ext.direct.Transaction} transaction
82299              * @param {Object} meta The meta data
82300              */            
82301             'call'
82302         );
82303         me.namespace = (Ext.isString(me.namespace)) ? Ext.ns(me.namespace) : me.namespace || window;
82304         me.transactions = Ext.create('Ext.util.MixedCollection');
82305         me.callBuffer = [];
82306     },
82307     
82308     /**
82309      * Initialize the API
82310      * @private
82311      */
82312     initAPI : function(){
82313         var actions = this.actions,
82314             namespace = this.namespace,
82315             action,
82316             cls,
82317             methods,
82318             i,
82319             len,
82320             method;
82321             
82322         for (action in actions) {
82323             cls = namespace[action];
82324             if (!cls) {
82325                 cls = namespace[action] = {};
82326             }
82327             methods = actions[action];
82328             
82329             for (i = 0, len = methods.length; i < len; ++i) {
82330                 method = Ext.create('Ext.direct.RemotingMethod', methods[i]);
82331                 cls[method.name] = this.createHandler(action, method);
82332             }
82333         }
82334     },
82335     
82336     /**
82337      * Create a handler function for a direct call.
82338      * @private
82339      * @param {String} action The action the call is for
82340      * @param {Object} method The details of the method
82341      * @return {Function} A JS function that will kick off the call
82342      */
82343     createHandler : function(action, method){
82344         var me = this,
82345             handler;
82346         
82347         if (!method.formHandler) {
82348             handler = function(){
82349                 me.configureRequest(action, method, Array.prototype.slice.call(arguments, 0));
82350             };
82351         } else {
82352             handler = function(form, callback, scope){
82353                 me.configureFormRequest(action, method, form, callback, scope);
82354             };
82355         }
82356         handler.directCfg = {
82357             action: action,
82358             method: method
82359         };
82360         return handler;
82361     },
82362     
82363     // inherit docs
82364     isConnected: function(){
82365         return !!this.connected;
82366     },
82367
82368     // inherit docs
82369     connect: function(){
82370         var me = this;
82371         
82372         if (me.url) {
82373             me.initAPI();
82374             me.connected = true;
82375             me.fireEvent('connect', me);
82376         } else if(!me.url) {
82377         }
82378     },
82379
82380     // inherit docs
82381     disconnect: function(){
82382         var me = this;
82383         
82384         if (me.connected) {
82385             me.connected = false;
82386             me.fireEvent('disconnect', me);
82387         }
82388     },
82389     
82390     /**
82391      * Run any callbacks related to the transaction.
82392      * @private
82393      * @param {Ext.direct.Transaction} transaction The transaction
82394      * @param {Ext.direct.Event} event The event
82395      */
82396     runCallback: function(transaction, event){
82397         var funcName = event.status ? 'success' : 'failure',
82398             callback,
82399             result;
82400         
82401         if (transaction && transaction.callback) {
82402             callback = transaction.callback;
82403             result = Ext.isDefined(event.result) ? event.result : event.data;
82404         
82405             if (Ext.isFunction(callback)) {
82406                 callback(result, event);
82407             } else {
82408                 Ext.callback(callback[funcName], callback.scope, [result, event]);
82409                 Ext.callback(callback.callback, callback.scope, [result, event]);
82410             }
82411         }
82412     },
82413     
82414     /**
82415      * React to the ajax request being completed
82416      * @private
82417      */
82418     onData: function(options, success, response){
82419         var me = this,
82420             i = 0,
82421             len,
82422             events,
82423             event,
82424             transaction,
82425             transactions;
82426             
82427         if (success) {
82428             events = me.createEvents(response);
82429             for (len = events.length; i < len; ++i) {
82430                 event = events[i];
82431                 transaction = me.getTransaction(event);
82432                 me.fireEvent('data', me, event);
82433                 if (transaction) {
82434                     me.runCallback(transaction, event, true);
82435                     Ext.direct.Manager.removeTransaction(transaction);
82436                 }
82437             }
82438         } else {
82439             transactions = [].concat(options.transaction);
82440             for (len = transactions.length; i < len; ++i) {
82441                 transaction = me.getTransaction(transactions[i]);
82442                 if (transaction && transaction.retryCount < me.maxRetries) {
82443                     transaction.retry();
82444                 } else {
82445                     event = Ext.create('Ext.direct.ExceptionEvent', {
82446                         data: null,
82447                         transaction: transaction,
82448                         code: Ext.direct.Manager.self.exceptions.TRANSPORT,
82449                         message: 'Unable to connect to the server.',
82450                         xhr: response
82451                     });
82452                     me.fireEvent('data', me, event);
82453                     if (transaction) {
82454                         me.runCallback(transaction, event, false);
82455                         Ext.direct.Manager.removeTransaction(transaction);
82456                     }
82457                 }
82458             }
82459         }
82460     },
82461     
82462     /**
82463      * Get transaction from XHR options
82464      * @private
82465      * @param {Object} options The options sent to the Ajax request
82466      * @return {Ext.direct.Transaction} The transaction, null if not found
82467      */
82468     getTransaction: function(options){
82469         return options && options.tid ? Ext.direct.Manager.getTransaction(options.tid) : null;
82470     },
82471     
82472     /**
82473      * Configure a direct request
82474      * @private
82475      * @param {String} action The action being executed
82476      * @param {Object} method The being executed
82477      */
82478     configureRequest: function(action, method, args){
82479         var me = this,
82480             callData = method.getCallData(args),
82481             data = callData.data, 
82482             callback = callData.callback, 
82483             scope = callData.scope,
82484             transaction;
82485
82486         transaction = Ext.create('Ext.direct.Transaction', {
82487             provider: me,
82488             args: args,
82489             action: action,
82490             method: method.name,
82491             data: data,
82492             callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback
82493         });
82494
82495         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
82496             Ext.direct.Manager.addTransaction(transaction);
82497             me.queueTransaction(transaction);
82498             me.fireEvent('call', me, transaction, method);
82499         }
82500     },
82501     
82502     /**
82503      * Gets the Ajax call info for a transaction
82504      * @private
82505      * @param {Ext.direct.Transaction} transaction The transaction
82506      * @return {Object} The call params
82507      */
82508     getCallData: function(transaction){
82509         return {
82510             action: transaction.action,
82511             method: transaction.method,
82512             data: transaction.data,
82513             type: 'rpc',
82514             tid: transaction.id
82515         };
82516     },
82517     
82518     /**
82519      * Sends a request to the server
82520      * @private
82521      * @param {Object/Array} data The data to send
82522      */
82523     sendRequest : function(data){
82524         var me = this,
82525             request = {
82526                 url: me.url,
82527                 callback: me.onData,
82528                 scope: me,
82529                 transaction: data,
82530                 timeout: me.timeout
82531             }, callData,
82532             enableUrlEncode = me.enableUrlEncode,
82533             i = 0,
82534             len,
82535             params;
82536             
82537
82538         if (Ext.isArray(data)) {
82539             callData = [];
82540             for (len = data.length; i < len; ++i) {
82541                 callData.push(me.getCallData(data[i]));
82542             }
82543         } else {
82544             callData = me.getCallData(data);
82545         }
82546
82547         if (enableUrlEncode) {
82548             params = {};
82549             params[Ext.isString(enableUrlEncode) ? enableUrlEncode : 'data'] = Ext.encode(callData);
82550             request.params = params;
82551         } else {
82552             request.jsonData = callData;
82553         }
82554         Ext.Ajax.request(request);
82555     },
82556     
82557     /**
82558      * Add a new transaction to the queue
82559      * @private
82560      * @param {Ext.direct.Transaction} transaction The transaction
82561      */
82562     queueTransaction: function(transaction){
82563         var me = this,
82564             enableBuffer = me.enableBuffer;
82565         
82566         if (transaction.form) {
82567             me.sendFormRequest(transaction);
82568             return;
82569         }
82570         
82571         me.callBuffer.push(transaction);
82572         if (enableBuffer) {
82573             if (!me.callTask) {
82574                 me.callTask = Ext.create('Ext.util.DelayedTask', me.combineAndSend, me);
82575             }
82576             me.callTask.delay(Ext.isNumber(enableBuffer) ? enableBuffer : 10);
82577         } else {
82578             me.combineAndSend();
82579         }
82580     },
82581     
82582     /**
82583      * Combine any buffered requests and send them off
82584      * @private
82585      */
82586     combineAndSend : function(){
82587         var buffer = this.callBuffer,
82588             len = buffer.length;
82589             
82590         if (len > 0) {
82591             this.sendRequest(len == 1 ? buffer[0] : buffer);
82592             this.callBuffer = [];
82593         }
82594     },
82595     
82596     /**
82597      * Configure a form submission request
82598      * @private
82599      * @param {String} action The action being executed
82600      * @param {Object} method The method being executed
82601      * @param {HTMLElement} form The form being submitted
82602      * @param {Function} callback (optional) A callback to run after the form submits
82603      * @param {Object} scope (optional) A scope to execute the callback in
82604      */
82605     configureFormRequest : function(action, method, form, callback, scope){
82606         var me = this,
82607             transaction = Ext.create('Ext.direct.Transaction', {
82608                 provider: me,
82609                 action: action,
82610                 method: method.name,
82611                 args: [form, callback, scope],
82612                 callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback,
82613                 isForm: true
82614             }),
82615             isUpload,
82616             params;
82617
82618         if (me.fireEvent('beforecall', me, transaction, method) !== false) {
82619             Ext.direct.Manager.addTransaction(transaction);
82620             isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data';
82621             
82622             params = {
82623                 extTID: transaction.id,
82624                 extAction: action,
82625                 extMethod: method.name,
82626                 extType: 'rpc',
82627                 extUpload: String(isUpload)
82628             };
82629             
82630             // change made from typeof callback check to callback.params
82631             // to support addl param passing in DirectSubmit EAC 6/2
82632             Ext.apply(transaction, {
82633                 form: Ext.getDom(form),
82634                 isUpload: isUpload,
82635                 params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
82636             });
82637             me.fireEvent('call', me, transaction, method);
82638             me.sendFormRequest(transaction);
82639         }
82640     },
82641     
82642     /**
82643      * Sends a form request
82644      * @private
82645      * @param {Ext.direct.Transaction} transaction The transaction to send
82646      */
82647     sendFormRequest: function(transaction){
82648         Ext.Ajax.request({
82649             url: this.url,
82650             params: transaction.params,
82651             callback: this.onData,
82652             scope: this,
82653             form: transaction.form,
82654             isUpload: transaction.isUpload,
82655             transaction: transaction
82656         });
82657     }
82658     
82659 });
82660
82661 /*
82662  * @class Ext.draw.Matrix
82663  * @private
82664  */
82665 Ext.define('Ext.draw.Matrix', {
82666
82667     /* Begin Definitions */
82668
82669     requires: ['Ext.draw.Draw'],
82670
82671     /* End Definitions */
82672
82673     constructor: function(a, b, c, d, e, f) {
82674         if (a != null) {
82675             this.matrix = [[a, c, e], [b, d, f], [0, 0, 1]];
82676         }
82677         else {
82678             this.matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
82679         }
82680     },
82681
82682     add: function(a, b, c, d, e, f) {
82683         var me = this,
82684             out = [[], [], []],
82685             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
82686             x,
82687             y,
82688             z,
82689             res;
82690
82691         for (x = 0; x < 3; x++) {
82692             for (y = 0; y < 3; y++) {
82693                 res = 0;
82694                 for (z = 0; z < 3; z++) {
82695                     res += me.matrix[x][z] * matrix[z][y];
82696                 }
82697                 out[x][y] = res;
82698             }
82699         }
82700         me.matrix = out;
82701     },
82702
82703     prepend: function(a, b, c, d, e, f) {
82704         var me = this,
82705             out = [[], [], []],
82706             matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
82707             x,
82708             y,
82709             z,
82710             res;
82711
82712         for (x = 0; x < 3; x++) {
82713             for (y = 0; y < 3; y++) {
82714                 res = 0;
82715                 for (z = 0; z < 3; z++) {
82716                     res += matrix[x][z] * me.matrix[z][y];
82717                 }
82718                 out[x][y] = res;
82719             }
82720         }
82721         me.matrix = out;
82722     },
82723
82724     invert: function() {
82725         var matrix = this.matrix,
82726             a = matrix[0][0],
82727             b = matrix[1][0],
82728             c = matrix[0][1],
82729             d = matrix[1][1],
82730             e = matrix[0][2],
82731             f = matrix[1][2],
82732             x = a * d - b * c;
82733         return new Ext.draw.Matrix(d / x, -b / x, -c / x, a / x, (c * f - d * e) / x, (b * e - a * f) / x);
82734     },
82735
82736     clone: function() {
82737         var matrix = this.matrix,
82738             a = matrix[0][0],
82739             b = matrix[1][0],
82740             c = matrix[0][1],
82741             d = matrix[1][1],
82742             e = matrix[0][2],
82743             f = matrix[1][2];
82744         return new Ext.draw.Matrix(a, b, c, d, e, f);
82745     },
82746
82747     translate: function(x, y) {
82748         this.prepend(1, 0, 0, 1, x, y);
82749     },
82750
82751     scale: function(x, y, cx, cy) {
82752         var me = this;
82753         if (y == null) {
82754             y = x;
82755         }
82756         me.add(1, 0, 0, 1, cx, cy);
82757         me.add(x, 0, 0, y, 0, 0);
82758         me.add(1, 0, 0, 1, -cx, -cy);
82759     },
82760
82761     rotate: function(a, x, y) {
82762         a = Ext.draw.Draw.rad(a);
82763         var me = this,
82764             cos = +Math.cos(a).toFixed(9),
82765             sin = +Math.sin(a).toFixed(9);
82766         me.add(cos, sin, -sin, cos, x, y);
82767         me.add(1, 0, 0, 1, -x, -y);
82768     },
82769
82770     x: function(x, y) {
82771         var matrix = this.matrix;
82772         return x * matrix[0][0] + y * matrix[0][1] + matrix[0][2];
82773     },
82774
82775     y: function(x, y) {
82776         var matrix = this.matrix;
82777         return x * matrix[1][0] + y * matrix[1][1] + matrix[1][2];
82778     },
82779
82780     get: function(i, j) {
82781         return + this.matrix[i][j].toFixed(4);
82782     },
82783
82784     toString: function() {
82785         var me = this;
82786         return [me.get(0, 0), me.get(0, 1), me.get(1, 0), me.get(1, 1), 0, 0].join();
82787     },
82788
82789     toSvg: function() {
82790         var me = this;
82791         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() + ")";
82792     },
82793
82794     toFilter: function() {
82795         var me = this;
82796         return "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand',FilterType=bilinear,M11=" + me.get(0, 0) +
82797             ", M12=" + me.get(0, 1) + ", M21=" + me.get(1, 0) + ", M22=" + me.get(1, 1) +
82798             ", Dx=" + me.get(0, 2) + ", Dy=" + me.get(1, 2) + ")";
82799     },
82800
82801     offset: function() {
82802         var matrix = this.matrix;
82803         return [(matrix[0][2] || 0).toFixed(4), (matrix[1][2] || 0).toFixed(4)];
82804     },
82805
82806     // Split matrix into Translate Scale, Shear, and Rotate
82807     split: function () {
82808         function norm(a) {
82809             return a[0] * a[0] + a[1] * a[1];
82810         }
82811         function normalize(a) {
82812             var mag = Math.sqrt(norm(a));
82813             a[0] /= mag;
82814             a[1] /= mag;
82815         }
82816         var matrix = this.matrix,
82817             out = {
82818                 translateX: matrix[0][2],
82819                 translateY: matrix[1][2]
82820             },
82821             row;
82822
82823         // scale and shear
82824         row = [[matrix[0][0], matrix[0][1]], [matrix[1][0], matrix[1][1]]];
82825         out.scaleX = Math.sqrt(norm(row[0]));
82826         normalize(row[0]);
82827
82828         out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
82829         row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
82830
82831         out.scaleY = Math.sqrt(norm(row[1]));
82832         normalize(row[1]);
82833         out.shear /= out.scaleY;
82834
82835         // rotation
82836         out.rotate = Math.asin(-row[0][1]);
82837
82838         out.isSimple = !+out.shear.toFixed(9) && (out.scaleX.toFixed(9) == out.scaleY.toFixed(9) || !out.rotate);
82839
82840         return out;
82841     }
82842 });
82843
82844 // private - DD implementation for Panels
82845 Ext.define('Ext.draw.SpriteDD', {
82846     extend: 'Ext.dd.DragSource',
82847
82848     constructor : function(sprite, cfg){
82849         var me = this,
82850             el = sprite.el;
82851         me.sprite = sprite;
82852         me.el = el;
82853         me.dragData = {el: el, sprite: sprite};
82854         me.callParent([el, cfg]);
82855         me.sprite.setStyle('cursor', 'move');
82856     },
82857
82858     showFrame: Ext.emptyFn,
82859     createFrame : Ext.emptyFn,
82860
82861     getDragEl : function(e){
82862         return this.el;
82863     },
82864     
82865     getRegion: function() {
82866         var me = this,
82867             el = me.el,
82868             pos, x1, x2, y1, y2, t, r, b, l, bbox, sprite;
82869         
82870         sprite = me.sprite;
82871         bbox = sprite.getBBox();
82872         
82873         try {
82874             pos = Ext.Element.getXY(el);
82875         } catch (e) { }
82876
82877         if (!pos) {
82878             return null;
82879         }
82880
82881         x1 = pos[0];
82882         x2 = x1 + bbox.width;
82883         y1 = pos[1];
82884         y2 = y1 + bbox.height;
82885         
82886         return Ext.create('Ext.util.Region', y1, x2, y2, x1);
82887     },
82888
82889     /*
82890       TODO(nico): Cumulative translations in VML are handled
82891       differently than in SVG. While in SVG we specify the translation
82892       relative to the original x, y position attributes, in VML the translation
82893       is a delta between the last position of the object (modified by the last
82894       translation) and the new one.
82895       
82896       In VML the translation alters the position
82897       of the object, we should change that or alter the SVG impl.
82898     */
82899      
82900     startDrag: function(x, y) {
82901         var me = this,
82902             attr = me.sprite.attr;
82903         me.prev = me.sprite.surface.transformToViewBox(x, y);
82904     },
82905
82906     onDrag: function(e) {
82907         var xy = e.getXY(),
82908             me = this,
82909             sprite = me.sprite,
82910             attr = sprite.attr, dx, dy;
82911         xy = me.sprite.surface.transformToViewBox(xy[0], xy[1]);
82912         dx = xy[0] - me.prev[0];
82913         dy = xy[1] - me.prev[1];
82914         sprite.setAttributes({
82915             translate: {
82916                 x: attr.translation.x + dx,
82917                 y: attr.translation.y + dy
82918             }
82919         }, true);
82920         me.prev = xy;
82921     },
82922
82923     setDragElPos: function () {
82924         // Disable automatic DOM move in DD that spoils layout of VML engine.
82925         return false;
82926     }
82927 });
82928 /**
82929  * A Sprite is an object rendered in a Drawing surface.
82930  *
82931  * # Translation
82932  *
82933  * For translate, the configuration object contains x and y attributes that indicate where to
82934  * translate the object. For example:
82935  *
82936  *     sprite.setAttributes({
82937  *       translate: {
82938  *        x: 10,
82939  *        y: 10
82940  *       }
82941  *     }, true);
82942  *
82943  *
82944  * # Rotation
82945  *
82946  * For rotation, the configuration object contains x and y attributes for the center of the rotation (which are optional),
82947  * and a `degrees` attribute that specifies the rotation in degrees. For example:
82948  *
82949  *     sprite.setAttributes({
82950  *       rotate: {
82951  *        degrees: 90
82952  *       }
82953  *     }, true);
82954  *
82955  * That example will create a 90 degrees rotation using the centroid of the Sprite as center of rotation, whereas:
82956  *
82957  *     sprite.setAttributes({
82958  *       rotate: {
82959  *        x: 0,
82960  *        y: 0,
82961  *        degrees: 90
82962  *       }
82963  *     }, true);
82964  *
82965  * will create a rotation around the `(0, 0)` axis.
82966  *
82967  *
82968  * # Scaling
82969  *
82970  * For scaling, the configuration object contains x and y attributes for the x-axis and y-axis scaling. For example:
82971  *
82972  *     sprite.setAttributes({
82973  *       scale: {
82974  *        x: 10,
82975  *        y: 3
82976  *       }
82977  *     }, true);
82978  *
82979  * You can also specify the center of scaling by adding `cx` and `cy` as properties:
82980  *
82981  *     sprite.setAttributes({
82982  *       scale: {
82983  *        cx: 0,
82984  *        cy: 0,
82985  *        x: 10,
82986  *        y: 3
82987  *       }
82988  *     }, true);
82989  *
82990  * That last example will scale a sprite taking as centers of scaling the `(0, 0)` coordinate.
82991  *
82992  *
82993  * # Creating and adding a Sprite to a Surface
82994  *
82995  * Sprites can be created with a reference to a {@link Ext.draw.Surface}
82996  *
82997  *     var drawComponent = Ext.create('Ext.draw.Component', options here...);
82998  *
82999  *     var sprite = Ext.create('Ext.draw.Sprite', {
83000  *         type: 'circle',
83001  *         fill: '#ff0',
83002  *         surface: drawComponent.surface,
83003  *         radius: 5
83004  *     });
83005  *
83006  * Sprites can also be added to the surface as a configuration object:
83007  *
83008  *     var sprite = drawComponent.surface.add({
83009  *         type: 'circle',
83010  *         fill: '#ff0',
83011  *         radius: 5
83012  *     });
83013  *
83014  * In order to properly apply properties and render the sprite we have to
83015  * `show` the sprite setting the option `redraw` to `true`:
83016  *
83017  *     sprite.show(true);
83018  *
83019  * The constructor configuration object of the Sprite can also be used and passed into the {@link Ext.draw.Surface}
83020  * add method to append a new sprite to the canvas. For example:
83021  *
83022  *     drawComponent.surface.add({
83023  *         type: 'circle',
83024  *         fill: '#ffc',
83025  *         radius: 100,
83026  *         x: 100,
83027  *         y: 100
83028  *     });
83029  */
83030 Ext.define('Ext.draw.Sprite', {
83031
83032     /* Begin Definitions */
83033
83034     mixins: {
83035         observable: 'Ext.util.Observable',
83036         animate: 'Ext.util.Animate'
83037     },
83038
83039     requires: ['Ext.draw.SpriteDD'],
83040
83041     /* End Definitions */
83042
83043     /**
83044      * @cfg {String} type The type of the sprite. Possible options are 'circle', 'path', 'rect', 'text', 'square', 'image'
83045      */
83046
83047     /**
83048      * @cfg {Number} width Used in rectangle sprites, the width of the rectangle
83049      */
83050
83051     /**
83052      * @cfg {Number} height Used in rectangle sprites, the height of the rectangle
83053      */
83054
83055     /**
83056      * @cfg {Number} size Used in square sprites, the dimension of the square
83057      */
83058
83059     /**
83060      * @cfg {Number} radius Used in circle sprites, the radius of the circle
83061      */
83062
83063     /**
83064      * @cfg {Number} x The position along the x-axis
83065      */
83066
83067     /**
83068      * @cfg {Number} y The position along the y-axis
83069      */
83070
83071     /**
83072      * @cfg {Array} path Used in path sprites, the path of the sprite written in SVG-like path syntax
83073      */
83074
83075     /**
83076      * @cfg {Number} opacity The opacity of the sprite
83077      */
83078
83079     /**
83080      * @cfg {String} fill The fill color
83081      */
83082
83083     /**
83084      * @cfg {String} stroke The stroke color
83085      */
83086
83087     /**
83088      * @cfg {Number} stroke-width The width of the stroke
83089      */
83090
83091     /**
83092      * @cfg {String} font Used with text type sprites. The full font description. Uses the same syntax as the CSS font parameter
83093      */
83094
83095     /**
83096      * @cfg {String} text Used with text type sprites. The text itself
83097      */
83098
83099     /**
83100      * @cfg {String/String[]} group The group that this sprite belongs to, or an array of groups. Only relevant when added to a
83101      * {@link Ext.draw.Surface}
83102      */
83103
83104     /**
83105      * @cfg {Boolean} draggable True to make the sprite draggable.
83106      */
83107
83108     dirty: false,
83109     dirtyHidden: false,
83110     dirtyTransform: false,
83111     dirtyPath: true,
83112     dirtyFont: true,
83113     zIndexDirty: true,
83114     isSprite: true,
83115     zIndex: 0,
83116     fontProperties: [
83117         'font',
83118         'font-size',
83119         'font-weight',
83120         'font-style',
83121         'font-family',
83122         'text-anchor',
83123         'text'
83124     ],
83125     pathProperties: [
83126         'x',
83127         'y',
83128         'd',
83129         'path',
83130         'height',
83131         'width',
83132         'radius',
83133         'r',
83134         'rx',
83135         'ry',
83136         'cx',
83137         'cy'
83138     ],
83139     constructor: function(config) {
83140         var me = this;
83141         config = config || {};
83142         me.id = Ext.id(null, 'ext-sprite-');
83143         me.transformations = [];
83144         Ext.copyTo(this, config, 'surface,group,type,draggable');
83145         //attribute bucket
83146         me.bbox = {};
83147         me.attr = {
83148             zIndex: 0,
83149             translation: {
83150                 x: null,
83151                 y: null
83152             },
83153             rotation: {
83154                 degrees: null,
83155                 x: null,
83156                 y: null
83157             },
83158             scaling: {
83159                 x: null,
83160                 y: null,
83161                 cx: null,
83162                 cy: null
83163             }
83164         };
83165         //delete not bucket attributes
83166         delete config.surface;
83167         delete config.group;
83168         delete config.type;
83169         delete config.draggable;
83170         me.setAttributes(config);
83171         me.addEvents(
83172             'beforedestroy',
83173             'destroy',
83174             'render',
83175             'mousedown',
83176             'mouseup',
83177             'mouseover',
83178             'mouseout',
83179             'mousemove',
83180             'click'
83181         );
83182         me.mixins.observable.constructor.apply(this, arguments);
83183     },
83184
83185     /**
83186      * @property {Ext.dd.DragSource} dd
83187      * If this Sprite is configured {@link #draggable}, this property will contain
83188      * an instance of {@link Ext.dd.DragSource} which handles dragging the Sprite.
83189      *
83190      * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
83191      * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
83192      */
83193
83194     initDraggable: function() {
83195         var me = this;
83196         me.draggable = true;
83197         //create element if it doesn't exist.
83198         if (!me.el) {
83199             me.surface.createSpriteElement(me);
83200         }
83201         me.dd = Ext.create('Ext.draw.SpriteDD', me, Ext.isBoolean(me.draggable) ? null : me.draggable);
83202         me.on('beforedestroy', me.dd.destroy, me.dd);
83203     },
83204
83205     /**
83206      * Change the attributes of the sprite.
83207      * @param {Object} attrs attributes to be changed on the sprite.
83208      * @param {Boolean} redraw Flag to immediatly draw the change.
83209      * @return {Ext.draw.Sprite} this
83210      */
83211     setAttributes: function(attrs, redraw) {
83212         var me = this,
83213             fontProps = me.fontProperties,
83214             fontPropsLength = fontProps.length,
83215             pathProps = me.pathProperties,
83216             pathPropsLength = pathProps.length,
83217             hasSurface = !!me.surface,
83218             custom = hasSurface && me.surface.customAttributes || {},
83219             spriteAttrs = me.attr,
83220             attr, i, translate, translation, rotate, rotation, scale, scaling;
83221
83222         attrs = Ext.apply({}, attrs);
83223         for (attr in custom) {
83224             if (attrs.hasOwnProperty(attr) && typeof custom[attr] == "function") {
83225                 Ext.apply(attrs, custom[attr].apply(me, [].concat(attrs[attr])));
83226             }
83227         }
83228
83229         // Flag a change in hidden
83230         if (!!attrs.hidden !== !!spriteAttrs.hidden) {
83231             me.dirtyHidden = true;
83232         }
83233
83234         // Flag path change
83235         for (i = 0; i < pathPropsLength; i++) {
83236             attr = pathProps[i];
83237             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
83238                 me.dirtyPath = true;
83239                 break;
83240             }
83241         }
83242
83243         // Flag zIndex change
83244         if ('zIndex' in attrs) {
83245             me.zIndexDirty = true;
83246         }
83247
83248         // Flag font/text change
83249         for (i = 0; i < fontPropsLength; i++) {
83250             attr = fontProps[i];
83251             if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
83252                 me.dirtyFont = true;
83253                 break;
83254             }
83255         }
83256
83257         translate = attrs.translate;
83258         translation = spriteAttrs.translation;
83259         if (translate) {
83260             if ((translate.x && translate.x !== translation.x) ||
83261                 (translate.y && translate.y !== translation.y)) {
83262                 Ext.apply(translation, translate);
83263                 me.dirtyTransform = true;
83264             }
83265             delete attrs.translate;
83266         }
83267
83268         rotate = attrs.rotate;
83269         rotation = spriteAttrs.rotation;
83270         if (rotate) {
83271             if ((rotate.x && rotate.x !== rotation.x) ||
83272                 (rotate.y && rotate.y !== rotation.y) ||
83273                 (rotate.degrees && rotate.degrees !== rotation.degrees)) {
83274                 Ext.apply(rotation, rotate);
83275                 me.dirtyTransform = true;
83276             }
83277             delete attrs.rotate;
83278         }
83279
83280         scale = attrs.scale;
83281         scaling = spriteAttrs.scaling;
83282         if (scale) {
83283             if ((scale.x && scale.x !== scaling.x) ||
83284                 (scale.y && scale.y !== scaling.y) ||
83285                 (scale.cx && scale.cx !== scaling.cx) ||
83286                 (scale.cy && scale.cy !== scaling.cy)) {
83287                 Ext.apply(scaling, scale);
83288                 me.dirtyTransform = true;
83289             }
83290             delete attrs.scale;
83291         }
83292
83293         Ext.apply(spriteAttrs, attrs);
83294         me.dirty = true;
83295
83296         if (redraw === true && hasSurface) {
83297             me.redraw();
83298         }
83299         return this;
83300     },
83301
83302     /**
83303      * Retrieves the bounding box of the sprite.
83304      * This will be returned as an object with x, y, width, and height properties.
83305      * @return {Object} bbox
83306      */
83307     getBBox: function() {
83308         return this.surface.getBBox(this);
83309     },
83310
83311     setText: function(text) {
83312         return this.surface.setText(this, text);
83313     },
83314
83315     /**
83316      * Hides the sprite.
83317      * @param {Boolean} redraw Flag to immediatly draw the change.
83318      * @return {Ext.draw.Sprite} this
83319      */
83320     hide: function(redraw) {
83321         this.setAttributes({
83322             hidden: true
83323         }, redraw);
83324         return this;
83325     },
83326
83327     /**
83328      * Shows the sprite.
83329      * @param {Boolean} redraw Flag to immediatly draw the change.
83330      * @return {Ext.draw.Sprite} this
83331      */
83332     show: function(redraw) {
83333         this.setAttributes({
83334             hidden: false
83335         }, redraw);
83336         return this;
83337     },
83338
83339     /**
83340      * Removes the sprite.
83341      */
83342     remove: function() {
83343         if (this.surface) {
83344             this.surface.remove(this);
83345             return true;
83346         }
83347         return false;
83348     },
83349
83350     onRemove: function() {
83351         this.surface.onRemove(this);
83352     },
83353
83354     /**
83355      * Removes the sprite and clears all listeners.
83356      */
83357     destroy: function() {
83358         var me = this;
83359         if (me.fireEvent('beforedestroy', me) !== false) {
83360             me.remove();
83361             me.surface.onDestroy(me);
83362             me.clearListeners();
83363             me.fireEvent('destroy');
83364         }
83365     },
83366
83367     /**
83368      * Redraws the sprite.
83369      * @return {Ext.draw.Sprite} this
83370      */
83371     redraw: function() {
83372         this.surface.renderItem(this);
83373         return this;
83374     },
83375
83376     /**
83377      * Wrapper for setting style properties, also takes single object parameter of multiple styles.
83378      * @param {String/Object} property The style property to be set, or an object of multiple styles.
83379      * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
83380      * @return {Ext.draw.Sprite} this
83381      */
83382     setStyle: function() {
83383         this.el.setStyle.apply(this.el, arguments);
83384         return this;
83385     },
83386
83387     /**
83388      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.  Note this method
83389      * is severly limited in VML.
83390      * @param {String/String[]} className The CSS class to add, or an array of classes
83391      * @return {Ext.draw.Sprite} this
83392      */
83393     addCls: function(obj) {
83394         this.surface.addCls(this, obj);
83395         return this;
83396     },
83397
83398     /**
83399      * Removes one or more CSS classes from the element.
83400      * @param {String/String[]} className The CSS class to remove, or an array of classes.  Note this method
83401      * is severly limited in VML.
83402      * @return {Ext.draw.Sprite} this
83403      */
83404     removeCls: function(obj) {
83405         this.surface.removeCls(this, obj);
83406         return this;
83407     }
83408 });
83409
83410 /**
83411  * @class Ext.draw.engine.Svg
83412  * @extends Ext.draw.Surface
83413  * Provides specific methods to draw with SVG.
83414  */
83415 Ext.define('Ext.draw.engine.Svg', {
83416
83417     /* Begin Definitions */
83418
83419     extend: 'Ext.draw.Surface',
83420
83421     requires: ['Ext.draw.Draw', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
83422
83423     /* End Definitions */
83424
83425     engine: 'Svg',
83426
83427     trimRe: /^\s+|\s+$/g,
83428     spacesRe: /\s+/,
83429     xlink: "http:/" + "/www.w3.org/1999/xlink",
83430
83431     translateAttrs: {
83432         radius: "r",
83433         radiusX: "rx",
83434         radiusY: "ry",
83435         path: "d",
83436         lineWidth: "stroke-width",
83437         fillOpacity: "fill-opacity",
83438         strokeOpacity: "stroke-opacity",
83439         strokeLinejoin: "stroke-linejoin"
83440     },
83441     
83442     parsers: {},
83443
83444     minDefaults: {
83445         circle: {
83446             cx: 0,
83447             cy: 0,
83448             r: 0,
83449             fill: "none",
83450             stroke: null,
83451             "stroke-width": null,
83452             opacity: null,
83453             "fill-opacity": null,
83454             "stroke-opacity": null
83455         },
83456         ellipse: {
83457             cx: 0,
83458             cy: 0,
83459             rx: 0,
83460             ry: 0,
83461             fill: "none",
83462             stroke: null,
83463             "stroke-width": null,
83464             opacity: null,
83465             "fill-opacity": null,
83466             "stroke-opacity": null
83467         },
83468         rect: {
83469             x: 0,
83470             y: 0,
83471             width: 0,
83472             height: 0,
83473             rx: 0,
83474             ry: 0,
83475             fill: "none",
83476             stroke: null,
83477             "stroke-width": null,
83478             opacity: null,
83479             "fill-opacity": null,
83480             "stroke-opacity": null
83481         },
83482         text: {
83483             x: 0,
83484             y: 0,
83485             "text-anchor": "start",
83486             "font-family": null,
83487             "font-size": null,
83488             "font-weight": null,
83489             "font-style": null,
83490             fill: "#000",
83491             stroke: null,
83492             "stroke-width": null,
83493             opacity: null,
83494             "fill-opacity": null,
83495             "stroke-opacity": null
83496         },
83497         path: {
83498             d: "M0,0",
83499             fill: "none",
83500             stroke: null,
83501             "stroke-width": null,
83502             opacity: null,
83503             "fill-opacity": null,
83504             "stroke-opacity": null
83505         },
83506         image: {
83507             x: 0,
83508             y: 0,
83509             width: 0,
83510             height: 0,
83511             preserveAspectRatio: "none",
83512             opacity: null
83513         }
83514     },
83515
83516     createSvgElement: function(type, attrs) {
83517         var el = this.domRef.createElementNS("http:/" + "/www.w3.org/2000/svg", type),
83518             key;
83519         if (attrs) {
83520             for (key in attrs) {
83521                 el.setAttribute(key, String(attrs[key]));
83522             }
83523         }
83524         return el;
83525     },
83526
83527     createSpriteElement: function(sprite) {
83528         // Create svg element and append to the DOM.
83529         var el = this.createSvgElement(sprite.type);
83530         el.id = sprite.id;
83531         if (el.style) {
83532             el.style.webkitTapHighlightColor = "rgba(0,0,0,0)";
83533         }
83534         sprite.el = Ext.get(el);
83535         this.applyZIndex(sprite); //performs the insertion
83536         sprite.matrix = Ext.create('Ext.draw.Matrix');
83537         sprite.bbox = {
83538             plain: 0,
83539             transform: 0
83540         };
83541         sprite.fireEvent("render", sprite);
83542         return el;
83543     },
83544
83545     getBBox: function (sprite, isWithoutTransform) {
83546         var realPath = this["getPath" + sprite.type](sprite);
83547         if (isWithoutTransform) {
83548             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
83549             return sprite.bbox.plain;
83550         }
83551         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
83552         return sprite.bbox.transform;
83553     },
83554     
83555     getBBoxText: function (sprite) {
83556         var bbox = {},
83557             bb, height, width, i, ln, el;
83558
83559         if (sprite && sprite.el) {
83560             el = sprite.el.dom;
83561             try {
83562                 bbox = el.getBBox();
83563                 return bbox;
83564             } catch(e) {
83565                 // Firefox 3.0.x plays badly here
83566             }
83567             bbox = {x: bbox.x, y: Infinity, width: 0, height: 0};
83568             ln = el.getNumberOfChars();
83569             for (i = 0; i < ln; i++) {
83570                 bb = el.getExtentOfChar(i);
83571                 bbox.y = Math.min(bb.y, bbox.y);
83572                 height = bb.y + bb.height - bbox.y;
83573                 bbox.height = Math.max(bbox.height, height);
83574                 width = bb.x + bb.width - bbox.x;
83575                 bbox.width = Math.max(bbox.width, width);
83576             }
83577             return bbox;
83578         }
83579     },
83580
83581     hide: function() {
83582         Ext.get(this.el).hide();
83583     },
83584
83585     show: function() {
83586         Ext.get(this.el).show();
83587     },
83588
83589     hidePrim: function(sprite) {
83590         this.addCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
83591     },
83592
83593     showPrim: function(sprite) {
83594         this.removeCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
83595     },
83596
83597     getDefs: function() {
83598         return this._defs || (this._defs = this.createSvgElement("defs"));
83599     },
83600
83601     transform: function(sprite) {
83602         var me = this,
83603             matrix = Ext.create('Ext.draw.Matrix'),
83604             transforms = sprite.transformations,
83605             transformsLength = transforms.length,
83606             i = 0,
83607             transform, type;
83608             
83609         for (; i < transformsLength; i++) {
83610             transform = transforms[i];
83611             type = transform.type;
83612             if (type == "translate") {
83613                 matrix.translate(transform.x, transform.y);
83614             }
83615             else if (type == "rotate") {
83616                 matrix.rotate(transform.degrees, transform.x, transform.y);
83617             }
83618             else if (type == "scale") {
83619                 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
83620             }
83621         }
83622         sprite.matrix = matrix;
83623         sprite.el.set({transform: matrix.toSvg()});
83624     },
83625
83626     setSize: function(w, h) {
83627         var me = this,
83628             el = me.el;
83629         
83630         w = +w || me.width;
83631         h = +h || me.height;
83632         me.width = w;
83633         me.height = h;
83634
83635         el.setSize(w, h);
83636         el.set({
83637             width: w,
83638             height: h
83639         });
83640         me.callParent([w, h]);
83641     },
83642
83643     /**
83644      * Get the region for the surface's canvas area
83645      * @returns {Ext.util.Region}
83646      */
83647     getRegion: function() {
83648         // Mozilla requires using the background rect because the svg element returns an
83649         // incorrect region. Webkit gives no region for the rect and must use the svg element.
83650         var svgXY = this.el.getXY(),
83651             rectXY = this.bgRect.getXY(),
83652             max = Math.max,
83653             x = max(svgXY[0], rectXY[0]),
83654             y = max(svgXY[1], rectXY[1]);
83655         return {
83656             left: x,
83657             top: y,
83658             right: x + this.width,
83659             bottom: y + this.height
83660         };
83661     },
83662
83663     onRemove: function(sprite) {
83664         if (sprite.el) {
83665             sprite.el.remove();
83666             delete sprite.el;
83667         }
83668         this.callParent(arguments);
83669     },
83670     
83671     setViewBox: function(x, y, width, height) {
83672         if (isFinite(x) && isFinite(y) && isFinite(width) && isFinite(height)) {
83673             this.callParent(arguments);
83674             this.el.dom.setAttribute("viewBox", [x, y, width, height].join(" "));
83675         }
83676     },
83677
83678     render: function (container) {
83679         var me = this;
83680         if (!me.el) {
83681             var width = me.width || 10,
83682                 height = me.height || 10,
83683                 el = me.createSvgElement('svg', {
83684                     xmlns: "http:/" + "/www.w3.org/2000/svg",
83685                     version: 1.1,
83686                     width: width,
83687                     height: height
83688                 }),
83689                 defs = me.getDefs(),
83690
83691                 // Create a rect that is always the same size as the svg root; this serves 2 purposes:
83692                 // (1) It allows mouse events to be fired over empty areas in Webkit, and (2) we can
83693                 // use it rather than the svg element for retrieving the correct client rect of the
83694                 // surface in Mozilla (see https://bugzilla.mozilla.org/show_bug.cgi?id=530985)
83695                 bgRect = me.createSvgElement("rect", {
83696                     width: "100%",
83697                     height: "100%",
83698                     fill: "#000",
83699                     stroke: "none",
83700                     opacity: 0
83701                 }),
83702                 webkitRect;
83703             
83704                 if (Ext.isSafari3) {
83705                     // Rect that we will show/hide to fix old WebKit bug with rendering issues.
83706                     webkitRect = me.createSvgElement("rect", {
83707                         x: -10,
83708                         y: -10,
83709                         width: "110%",
83710                         height: "110%",
83711                         fill: "none",
83712                         stroke: "#000"
83713                     });
83714                 }
83715             el.appendChild(defs);
83716             if (Ext.isSafari3) {
83717                 el.appendChild(webkitRect);
83718             }
83719             el.appendChild(bgRect);
83720             container.appendChild(el);
83721             me.el = Ext.get(el);
83722             me.bgRect = Ext.get(bgRect);
83723             if (Ext.isSafari3) {
83724                 me.webkitRect = Ext.get(webkitRect);
83725                 me.webkitRect.hide();
83726             }
83727             me.el.on({
83728                 scope: me,
83729                 mouseup: me.onMouseUp,
83730                 mousedown: me.onMouseDown,
83731                 mouseover: me.onMouseOver,
83732                 mouseout: me.onMouseOut,
83733                 mousemove: me.onMouseMove,
83734                 mouseenter: me.onMouseEnter,
83735                 mouseleave: me.onMouseLeave,
83736                 click: me.onClick
83737             });
83738         }
83739         me.renderAll();
83740     },
83741
83742     // private
83743     onMouseEnter: function(e) {
83744         if (this.el.parent().getRegion().contains(e.getPoint())) {
83745             this.fireEvent('mouseenter', e);
83746         }
83747     },
83748
83749     // private
83750     onMouseLeave: function(e) {
83751         if (!this.el.parent().getRegion().contains(e.getPoint())) {
83752             this.fireEvent('mouseleave', e);
83753         }
83754     },
83755     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
83756     processEvent: function(name, e) {
83757         var target = e.getTarget(),
83758             surface = this.surface,
83759             sprite;
83760
83761         this.fireEvent(name, e);
83762         // We wrap text types in a tspan, sprite is the parent.
83763         if (target.nodeName == "tspan" && target.parentNode) {
83764             target = target.parentNode;
83765         }
83766         sprite = this.items.get(target.id);
83767         if (sprite) {
83768             sprite.fireEvent(name, sprite, e);
83769         }
83770     },
83771
83772     /* @private - Wrap SVG text inside a tspan to allow for line wrapping.  In addition this normallizes
83773      * the baseline for text the vertical middle of the text to be the same as VML.
83774      */
83775     tuneText: function (sprite, attrs) {
83776         var el = sprite.el.dom,
83777             tspans = [],
83778             height, tspan, text, i, ln, texts, factor;
83779
83780         if (attrs.hasOwnProperty("text")) {
83781            tspans = this.setText(sprite, attrs.text);
83782         }
83783         // Normalize baseline via a DY shift of first tspan. Shift other rows by height * line height (1.2)
83784         if (tspans.length) {
83785             height = this.getBBoxText(sprite).height;
83786             for (i = 0, ln = tspans.length; i < ln; i++) {
83787                 // The text baseline for FireFox 3.0 and 3.5 is different than other SVG implementations
83788                 // so we are going to normalize that here
83789                 factor = (Ext.isFF3_0 || Ext.isFF3_5) ? 2 : 4;
83790                 tspans[i].setAttribute("dy", i ? height * 1.2 : height / factor);
83791             }
83792             sprite.dirty = true;
83793         }
83794     },
83795
83796     setText: function(sprite, textString) {
83797          var me = this,
83798              el = sprite.el.dom,
83799              x = el.getAttribute("x"),
83800              tspans = [],
83801              height, tspan, text, i, ln, texts;
83802         
83803         while (el.firstChild) {
83804             el.removeChild(el.firstChild);
83805         }
83806         // Wrap each row into tspan to emulate rows
83807         texts = String(textString).split("\n");
83808         for (i = 0, ln = texts.length; i < ln; i++) {
83809             text = texts[i];
83810             if (text) {
83811                 tspan = me.createSvgElement("tspan");
83812                 tspan.appendChild(document.createTextNode(Ext.htmlDecode(text)));
83813                 tspan.setAttribute("x", x);
83814                 el.appendChild(tspan);
83815                 tspans[i] = tspan;
83816             }
83817         }
83818         return tspans;
83819     },
83820
83821     renderAll: function() {
83822         this.items.each(this.renderItem, this);
83823     },
83824
83825     renderItem: function (sprite) {
83826         if (!this.el) {
83827             return;
83828         }
83829         if (!sprite.el) {
83830             this.createSpriteElement(sprite);
83831         }
83832         if (sprite.zIndexDirty) {
83833             this.applyZIndex(sprite);
83834         }
83835         if (sprite.dirty) {
83836             this.applyAttrs(sprite);
83837             this.applyTransformations(sprite);
83838         }
83839     },
83840
83841     redraw: function(sprite) {
83842         sprite.dirty = sprite.zIndexDirty = true;
83843         this.renderItem(sprite);
83844     },
83845
83846     applyAttrs: function (sprite) {
83847         var me = this,
83848             el = sprite.el,
83849             group = sprite.group,
83850             sattr = sprite.attr,
83851             parsers = me.parsers,
83852             //Safari does not handle linear gradients correctly in quirksmode
83853             //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
83854             //ref: EXTJSIV-1472
83855             gradientsMap = me.gradientsMap || {},
83856             safariFix = Ext.isSafari && !Ext.isStrict,
83857             groups, i, ln, attrs, font, key, style, name, rect;
83858
83859         if (group) {
83860             groups = [].concat(group);
83861             ln = groups.length;
83862             for (i = 0; i < ln; i++) {
83863                 group = groups[i];
83864                 me.getGroup(group).add(sprite);
83865             }
83866             delete sprite.group;
83867         }
83868         attrs = me.scrubAttrs(sprite) || {};
83869
83870         // if (sprite.dirtyPath) {
83871             sprite.bbox.plain = 0;
83872             sprite.bbox.transform = 0;
83873             if (sprite.type == "circle" || sprite.type == "ellipse") {
83874                 attrs.cx = attrs.cx || attrs.x;
83875                 attrs.cy = attrs.cy || attrs.y;
83876             }
83877             else if (sprite.type == "rect") {
83878                 attrs.rx = attrs.ry = attrs.r;
83879             }
83880             else if (sprite.type == "path" && attrs.d) {
83881                 attrs.d = Ext.draw.Draw.pathToString(Ext.draw.Draw.pathToAbsolute(attrs.d));
83882             }
83883             sprite.dirtyPath = false;
83884         // }
83885         // else {
83886         //     delete attrs.d;
83887         // }
83888
83889         if (attrs['clip-rect']) {
83890             me.setClip(sprite, attrs);
83891             delete attrs['clip-rect'];
83892         }
83893         if (sprite.type == 'text' && attrs.font && sprite.dirtyFont) {
83894             el.set({ style: "font: " + attrs.font});
83895             sprite.dirtyFont = false;
83896         }
83897         if (sprite.type == "image") {
83898             el.dom.setAttributeNS(me.xlink, "href", attrs.src);
83899         }
83900         Ext.applyIf(attrs, me.minDefaults[sprite.type]);
83901
83902         if (sprite.dirtyHidden) {
83903             (sattr.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
83904             sprite.dirtyHidden = false;
83905         }
83906         for (key in attrs) {
83907             if (attrs.hasOwnProperty(key) && attrs[key] != null) {
83908                 //Safari does not handle linear gradients correctly in quirksmode
83909                 //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
83910                 //ref: EXTJSIV-1472
83911                 //if we're Safari in QuirksMode and we're applying some color attribute and the value of that
83912                 //attribute is a reference to a gradient then assign a plain color to that value instead of the gradient.
83913                 if (safariFix && ('color|stroke|fill'.indexOf(key) > -1) && (attrs[key] in gradientsMap)) {
83914                     attrs[key] = gradientsMap[attrs[key]];
83915                 }
83916                 if (key in parsers) {
83917                     el.dom.setAttribute(key, parsers[key](attrs[key], sprite, me));
83918                 } else {
83919                     el.dom.setAttribute(key, attrs[key]);
83920                 }
83921             }
83922         }
83923         
83924         if (sprite.type == 'text') {
83925             me.tuneText(sprite, attrs);
83926         }
83927
83928         //set styles
83929         style = sattr.style;
83930         if (style) {
83931             el.setStyle(style);
83932         }
83933
83934         sprite.dirty = false;
83935
83936         if (Ext.isSafari3) {
83937             // Refreshing the view to fix bug EXTJSIV-1: rendering issue in old Safari 3
83938             me.webkitRect.show();
83939             setTimeout(function () {
83940                 me.webkitRect.hide();
83941             });
83942         }
83943     },
83944
83945     setClip: function(sprite, params) {
83946         var me = this,
83947             rect = params["clip-rect"],
83948             clipEl, clipPath;
83949         if (rect) {
83950             if (sprite.clip) {
83951                 sprite.clip.parentNode.parentNode.removeChild(sprite.clip.parentNode);
83952             }
83953             clipEl = me.createSvgElement('clipPath');
83954             clipPath = me.createSvgElement('rect');
83955             clipEl.id = Ext.id(null, 'ext-clip-');
83956             clipPath.setAttribute("x", rect.x);
83957             clipPath.setAttribute("y", rect.y);
83958             clipPath.setAttribute("width", rect.width);
83959             clipPath.setAttribute("height", rect.height);
83960             clipEl.appendChild(clipPath);
83961             me.getDefs().appendChild(clipEl);
83962             sprite.el.dom.setAttribute("clip-path", "url(#" + clipEl.id + ")");
83963             sprite.clip = clipPath;
83964         }
83965         // if (!attrs[key]) {
83966         //     var clip = Ext.getDoc().dom.getElementById(sprite.el.getAttribute("clip-path").replace(/(^url\(#|\)$)/g, ""));
83967         //     clip && clip.parentNode.removeChild(clip);
83968         //     sprite.el.setAttribute("clip-path", "");
83969         //     delete attrss.clip;
83970         // }
83971     },
83972
83973     /**
83974      * Insert or move a given sprite's element to the correct place in the DOM list for its zIndex
83975      * @param {Ext.draw.Sprite} sprite
83976      */
83977     applyZIndex: function(sprite) {
83978         var me = this,
83979             items = me.items,
83980             idx = items.indexOf(sprite),
83981             el = sprite.el,
83982             prevEl;
83983         if (me.el.dom.childNodes[idx + 2] !== el.dom) { //shift by 2 to account for defs and bg rect
83984             if (idx > 0) {
83985                 // Find the first previous sprite which has its DOM element created already
83986                 do {
83987                     prevEl = items.getAt(--idx).el;
83988                 } while (!prevEl && idx > 0);
83989             }
83990             el.insertAfter(prevEl || me.bgRect);
83991         }
83992         sprite.zIndexDirty = false;
83993     },
83994
83995     createItem: function (config) {
83996         var sprite = Ext.create('Ext.draw.Sprite', config);
83997         sprite.surface = this;
83998         return sprite;
83999     },
84000
84001     addGradient: function(gradient) {
84002         gradient = Ext.draw.Draw.parseGradient(gradient);
84003         var me = this,
84004             ln = gradient.stops.length,
84005             vector = gradient.vector,
84006             //Safari does not handle linear gradients correctly in quirksmode
84007             //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
84008             //ref: EXTJSIV-1472
84009             usePlain = Ext.isSafari && !Ext.isStrict,
84010             gradientEl, stop, stopEl, i, gradientsMap;
84011             
84012         gradientsMap = me.gradientsMap || {};
84013         
84014         if (!usePlain) {
84015             if (gradient.type == "linear") {
84016                 gradientEl = me.createSvgElement("linearGradient");
84017                 gradientEl.setAttribute("x1", vector[0]);
84018                 gradientEl.setAttribute("y1", vector[1]);
84019                 gradientEl.setAttribute("x2", vector[2]);
84020                 gradientEl.setAttribute("y2", vector[3]);
84021             }
84022             else {
84023                 gradientEl = me.createSvgElement("radialGradient");
84024                 gradientEl.setAttribute("cx", gradient.centerX);
84025                 gradientEl.setAttribute("cy", gradient.centerY);
84026                 gradientEl.setAttribute("r", gradient.radius);
84027                 if (Ext.isNumber(gradient.focalX) && Ext.isNumber(gradient.focalY)) {
84028                     gradientEl.setAttribute("fx", gradient.focalX);
84029                     gradientEl.setAttribute("fy", gradient.focalY);
84030                 }
84031             }
84032             gradientEl.id = gradient.id;
84033             me.getDefs().appendChild(gradientEl);
84034             for (i = 0; i < ln; i++) {
84035                 stop = gradient.stops[i];
84036                 stopEl = me.createSvgElement("stop");
84037                 stopEl.setAttribute("offset", stop.offset + "%");
84038                 stopEl.setAttribute("stop-color", stop.color);
84039                 stopEl.setAttribute("stop-opacity",stop.opacity);
84040                 gradientEl.appendChild(stopEl);
84041             }
84042         } else {
84043             gradientsMap['url(#' + gradient.id + ')'] = gradient.stops[0].color;
84044         }
84045         me.gradientsMap = gradientsMap;
84046     },
84047
84048     /**
84049      * Checks if the specified CSS class exists on this element's DOM node.
84050      * @param {String} className The CSS class to check for
84051      * @return {Boolean} True if the class exists, else false
84052      */
84053     hasCls: function(sprite, className) {
84054         return className && (' ' + (sprite.el.dom.getAttribute('class') || '') + ' ').indexOf(' ' + className + ' ') != -1;
84055     },
84056
84057     addCls: function(sprite, className) {
84058         var el = sprite.el,
84059             i,
84060             len,
84061             v,
84062             cls = [],
84063             curCls =  el.getAttribute('class') || '';
84064         // Separate case is for speed
84065         if (!Ext.isArray(className)) {
84066             if (typeof className == 'string' && !this.hasCls(sprite, className)) {
84067                 el.set({ 'class': curCls + ' ' + className });
84068             }
84069         }
84070         else {
84071             for (i = 0, len = className.length; i < len; i++) {
84072                 v = className[i];
84073                 if (typeof v == 'string' && (' ' + curCls + ' ').indexOf(' ' + v + ' ') == -1) {
84074                     cls.push(v);
84075                 }
84076             }
84077             if (cls.length) {
84078                 el.set({ 'class': ' ' + cls.join(' ') });
84079             }
84080         }
84081     },
84082
84083     removeCls: function(sprite, className) {
84084         var me = this,
84085             el = sprite.el,
84086             curCls =  el.getAttribute('class') || '',
84087             i, idx, len, cls, elClasses;
84088         if (!Ext.isArray(className)){
84089             className = [className];
84090         }
84091         if (curCls) {
84092             elClasses = curCls.replace(me.trimRe, ' ').split(me.spacesRe);
84093             for (i = 0, len = className.length; i < len; i++) {
84094                 cls = className[i];
84095                 if (typeof cls == 'string') {
84096                     cls = cls.replace(me.trimRe, '');
84097                     idx = Ext.Array.indexOf(elClasses, cls);
84098                     if (idx != -1) {
84099                         Ext.Array.erase(elClasses, idx, 1);
84100                     }
84101                 }
84102             }
84103             el.set({ 'class': elClasses.join(' ') });
84104         }
84105     },
84106
84107     destroy: function() {
84108         var me = this;
84109         
84110         me.callParent();
84111         if (me.el) {
84112             me.el.remove();
84113         }
84114         delete me.el;
84115     }
84116 });
84117 /**
84118  * @class Ext.draw.engine.Vml
84119  * @extends Ext.draw.Surface
84120  * Provides specific methods to draw with VML.
84121  */
84122
84123 Ext.define('Ext.draw.engine.Vml', {
84124
84125     /* Begin Definitions */
84126
84127     extend: 'Ext.draw.Surface',
84128
84129     requires: ['Ext.draw.Draw', 'Ext.draw.Color', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
84130
84131     /* End Definitions */
84132
84133     engine: 'Vml',
84134
84135     map: {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
84136     bitesRe: /([clmz]),?([^clmz]*)/gi,
84137     valRe: /-?[^,\s-]+/g,
84138     fillUrlRe: /^url\(\s*['"]?([^\)]+?)['"]?\s*\)$/i,
84139     pathlike: /^(path|rect)$/,
84140     NonVmlPathRe: /[ahqstv]/ig, // Non-VML Pathing ops
84141     partialPathRe: /[clmz]/g,
84142     fontFamilyRe: /^['"]+|['"]+$/g,
84143     baseVmlCls: Ext.baseCSSPrefix + 'vml-base',
84144     vmlGroupCls: Ext.baseCSSPrefix + 'vml-group',
84145     spriteCls: Ext.baseCSSPrefix + 'vml-sprite',
84146     measureSpanCls: Ext.baseCSSPrefix + 'vml-measure-span',
84147     zoom: 21600,
84148     coordsize: 1000,
84149     coordorigin: '0 0',
84150
84151     // VML uses CSS z-index and therefore doesn't need sprites to be kept in zIndex order
84152     orderSpritesByZIndex: false,
84153
84154     // @private
84155     // Convert an SVG standard path into a VML path
84156     path2vml: function (path) {
84157         var me = this,
84158             nonVML =  me.NonVmlPathRe,
84159             map = me.map,
84160             val = me.valRe,
84161             zoom = me.zoom,
84162             bites = me.bitesRe,
84163             command = Ext.Function.bind(Ext.draw.Draw.pathToAbsolute, Ext.draw.Draw),
84164             res, pa, p, r, i, ii, j, jj;
84165         if (String(path).match(nonVML)) {
84166             command = Ext.Function.bind(Ext.draw.Draw.path2curve, Ext.draw.Draw);
84167         } else if (!String(path).match(me.partialPathRe)) {
84168             res = String(path).replace(bites, function (all, command, args) {
84169                 var vals = [],
84170                     isMove = command.toLowerCase() == "m",
84171                     res = map[command];
84172                 args.replace(val, function (value) {
84173                     if (isMove && vals[length] == 2) {
84174                         res += vals + map[command == "m" ? "l" : "L"];
84175                         vals = [];
84176                     }
84177                     vals.push(Math.round(value * zoom));
84178                 });
84179                 return res + vals;
84180             });
84181             return res;
84182         }
84183         pa = command(path);
84184         res = [];
84185         for (i = 0, ii = pa.length; i < ii; i++) {
84186             p = pa[i];
84187             r = pa[i][0].toLowerCase();
84188             if (r == "z") {
84189                 r = "x";
84190             }
84191             for (j = 1, jj = p.length; j < jj; j++) {
84192                 r += Math.round(p[j] * me.zoom) + (j != jj - 1 ? "," : "");
84193             }
84194             res.push(r);
84195         }
84196         return res.join(" ");
84197     },
84198
84199     // @private - set of attributes which need to be translated from the sprite API to the native browser API
84200     translateAttrs: {
84201         radius: "r",
84202         radiusX: "rx",
84203         radiusY: "ry",
84204         lineWidth: "stroke-width",
84205         fillOpacity: "fill-opacity",
84206         strokeOpacity: "stroke-opacity",
84207         strokeLinejoin: "stroke-linejoin"
84208     },
84209
84210     // @private - Minimun set of defaults for different types of sprites.
84211     minDefaults: {
84212         circle: {
84213             fill: "none",
84214             stroke: null,
84215             "stroke-width": null,
84216             opacity: null,
84217             "fill-opacity": null,
84218             "stroke-opacity": null
84219         },
84220         ellipse: {
84221             cx: 0,
84222             cy: 0,
84223             rx: 0,
84224             ry: 0,
84225             fill: "none",
84226             stroke: null,
84227             "stroke-width": null,
84228             opacity: null,
84229             "fill-opacity": null,
84230             "stroke-opacity": null
84231         },
84232         rect: {
84233             x: 0,
84234             y: 0,
84235             width: 0,
84236             height: 0,
84237             rx: 0,
84238             ry: 0,
84239             fill: "none",
84240             stroke: null,
84241             "stroke-width": null,
84242             opacity: null,
84243             "fill-opacity": null,
84244             "stroke-opacity": null
84245         },
84246         text: {
84247             x: 0,
84248             y: 0,
84249             "text-anchor": "start",
84250             font: '10px "Arial"',
84251             fill: "#000",
84252             stroke: null,
84253             "stroke-width": null,
84254             opacity: null,
84255             "fill-opacity": null,
84256             "stroke-opacity": null
84257         },
84258         path: {
84259             d: "M0,0",
84260             fill: "none",
84261             stroke: null,
84262             "stroke-width": null,
84263             opacity: null,
84264             "fill-opacity": null,
84265             "stroke-opacity": null
84266         },
84267         image: {
84268             x: 0,
84269             y: 0,
84270             width: 0,
84271             height: 0,
84272             preserveAspectRatio: "none",
84273             opacity: null
84274         }
84275     },
84276
84277     // private
84278     onMouseEnter: function(e) {
84279         this.fireEvent("mouseenter", e);
84280     },
84281
84282     // private
84283     onMouseLeave: function(e) {
84284         this.fireEvent("mouseleave", e);
84285     },
84286
84287     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
84288     processEvent: function(name, e) {
84289         var target = e.getTarget(),
84290             surface = this.surface,
84291             sprite;
84292         this.fireEvent(name, e);
84293         sprite = this.items.get(target.id);
84294         if (sprite) {
84295             sprite.fireEvent(name, sprite, e);
84296         }
84297     },
84298
84299     // Create the VML element/elements and append them to the DOM
84300     createSpriteElement: function(sprite) {
84301         var me = this,
84302             attr = sprite.attr,
84303             type = sprite.type,
84304             zoom = me.zoom,
84305             vml = sprite.vml || (sprite.vml = {}),
84306             round = Math.round,
84307             el = me.createNode('shape'),
84308             path, skew, textPath;
84309
84310         el.coordsize = zoom + ' ' + zoom;
84311         el.coordorigin = attr.coordorigin || "0 0";
84312         Ext.get(el).addCls(me.spriteCls);
84313         if (type == "text") {
84314             vml.path = path = me.createNode("path");
84315             path.textpathok = true;
84316             vml.textpath = textPath = me.createNode("textpath");
84317             textPath.on = true;
84318             el.appendChild(textPath);
84319             el.appendChild(path);
84320         }
84321         el.id = sprite.id;
84322         sprite.el = Ext.get(el);
84323         me.el.appendChild(el);
84324         if (type !== 'image') {
84325             skew = me.createNode("skew");
84326             skew.on = true;
84327             el.appendChild(skew);
84328             sprite.skew = skew;
84329         }
84330         sprite.matrix = Ext.create('Ext.draw.Matrix');
84331         sprite.bbox = {
84332             plain: null,
84333             transform: null
84334         };
84335         sprite.fireEvent("render", sprite);
84336         return sprite.el;
84337     },
84338
84339     // @private - Get bounding box for the sprite.  The Sprite itself has the public method.
84340     getBBox: function (sprite, isWithoutTransform) {
84341         var realPath = this["getPath" + sprite.type](sprite);
84342         if (isWithoutTransform) {
84343             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
84344             return sprite.bbox.plain;
84345         }
84346         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
84347         return sprite.bbox.transform;
84348     },
84349
84350     getBBoxText: function (sprite) {
84351         var vml = sprite.vml;
84352         return {
84353             x: vml.X + (vml.bbx || 0) - vml.W / 2,
84354             y: vml.Y - vml.H / 2,
84355             width: vml.W,
84356             height: vml.H
84357         };
84358     },
84359
84360     applyAttrs: function (sprite) {
84361         var me = this,
84362             vml = sprite.vml,
84363             group = sprite.group,
84364             spriteAttr = sprite.attr,
84365             el = sprite.el,
84366             dom = el.dom,
84367             style, name, groups, i, ln, scrubbedAttrs, font, key, bbox;
84368
84369         if (group) {
84370             groups = [].concat(group);
84371             ln = groups.length;
84372             for (i = 0; i < ln; i++) {
84373                 group = groups[i];
84374                 me.getGroup(group).add(sprite);
84375             }
84376             delete sprite.group;
84377         }
84378         scrubbedAttrs = me.scrubAttrs(sprite) || {};
84379
84380         if (sprite.zIndexDirty) {
84381             me.setZIndex(sprite);
84382         }
84383
84384         // Apply minimum default attributes
84385         Ext.applyIf(scrubbedAttrs, me.minDefaults[sprite.type]);
84386
84387         if (dom.href) {
84388             dom.href = scrubbedAttrs.href;
84389         }
84390         if (dom.title) {
84391             dom.title = scrubbedAttrs.title;
84392         }
84393         if (dom.target) {
84394             dom.target = scrubbedAttrs.target;
84395         }
84396         if (dom.cursor) {
84397             dom.cursor = scrubbedAttrs.cursor;
84398         }
84399
84400         // Change visibility
84401         if (sprite.dirtyHidden) {
84402             (scrubbedAttrs.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
84403             sprite.dirtyHidden = false;
84404         }
84405
84406         // Update path
84407         if (sprite.dirtyPath) {
84408             if (sprite.type == "circle" || sprite.type == "ellipse") {
84409                 var cx = scrubbedAttrs.x,
84410                     cy = scrubbedAttrs.y,
84411                     rx = scrubbedAttrs.rx || scrubbedAttrs.r || 0,
84412                     ry = scrubbedAttrs.ry || scrubbedAttrs.r || 0;
84413                 dom.path = Ext.String.format("ar{0},{1},{2},{3},{4},{1},{4},{1}",
84414                             Math.round((cx - rx) * me.zoom),
84415                             Math.round((cy - ry) * me.zoom),
84416                             Math.round((cx + rx) * me.zoom),
84417                             Math.round((cy + ry) * me.zoom),
84418                             Math.round(cx * me.zoom));
84419                 sprite.dirtyPath = false;
84420             }
84421             else if (sprite.type !== "text") {
84422                 sprite.attr.path = scrubbedAttrs.path = me.setPaths(sprite, scrubbedAttrs) || scrubbedAttrs.path;
84423                 dom.path = me.path2vml(scrubbedAttrs.path);
84424                 sprite.dirtyPath = false;
84425             }
84426         }
84427
84428         // Apply clipping
84429         if ("clip-rect" in scrubbedAttrs) {
84430             me.setClip(sprite, scrubbedAttrs);
84431         }
84432
84433         // Handle text (special handling required)
84434         if (sprite.type == "text") {
84435             me.setTextAttributes(sprite, scrubbedAttrs);
84436         }
84437
84438         // Handle fill and opacity
84439         if (sprite.type == 'image' || scrubbedAttrs.opacity  || scrubbedAttrs['fill-opacity'] || scrubbedAttrs.fill) {
84440             me.setFill(sprite, scrubbedAttrs);
84441         }
84442
84443         // Handle stroke (all fills require a stroke element)
84444         if (scrubbedAttrs.stroke || scrubbedAttrs['stroke-opacity'] || scrubbedAttrs.fill) {
84445             me.setStroke(sprite, scrubbedAttrs);
84446         }
84447         
84448         //set styles
84449         style = spriteAttr.style;
84450         if (style) {
84451             el.setStyle(style);
84452         }
84453
84454         sprite.dirty = false;
84455     },
84456
84457     setZIndex: function(sprite) {
84458         if (sprite.el) {
84459             if (sprite.attr.zIndex != undefined) {
84460                 sprite.el.setStyle('zIndex', sprite.attr.zIndex);
84461             }
84462             sprite.zIndexDirty = false;
84463         }
84464     },
84465
84466     // Normalize all virtualized types into paths.
84467     setPaths: function(sprite, params) {
84468         var spriteAttr = sprite.attr;
84469         // Clear bbox cache
84470         sprite.bbox.plain = null;
84471         sprite.bbox.transform = null;
84472         if (sprite.type == 'circle') {
84473             spriteAttr.rx = spriteAttr.ry = params.r;
84474             return Ext.draw.Draw.ellipsePath(sprite);
84475         }
84476         else if (sprite.type == 'ellipse') {
84477             spriteAttr.rx = params.rx;
84478             spriteAttr.ry = params.ry;
84479             return Ext.draw.Draw.ellipsePath(sprite);
84480         }
84481         else if (sprite.type == 'rect' || sprite.type == 'image') {
84482             spriteAttr.rx = spriteAttr.ry = params.r;
84483             return Ext.draw.Draw.rectPath(sprite);
84484         }
84485         else if (sprite.type == 'path' && spriteAttr.path) {
84486             return Ext.draw.Draw.pathToAbsolute(spriteAttr.path);
84487         }
84488         return false;
84489     },
84490
84491     setFill: function(sprite, params) {
84492         var me = this,
84493             el = sprite.el,
84494             dom = el.dom,
84495             fillEl = dom.getElementsByTagName('fill')[0],
84496             opacity, gradient, fillUrl, rotation, angle;
84497
84498         if (fillEl) {
84499             dom.removeChild(fillEl);
84500         } else {
84501             fillEl = me.createNode('fill');
84502         }
84503         if (Ext.isArray(params.fill)) {
84504             params.fill = params.fill[0];
84505         }
84506         if (sprite.type == 'image') {
84507             fillEl.on = true;
84508             fillEl.src = params.src;
84509             fillEl.type = "tile";
84510             fillEl.rotate = true;
84511         } else if (params.fill == "none") {
84512             fillEl.on = false;
84513         } else {
84514             if (typeof params.opacity == "number") {
84515                 fillEl.opacity = params.opacity;
84516             }
84517             if (typeof params["fill-opacity"] == "number") {
84518                 fillEl.opacity = params["fill-opacity"];
84519             }
84520             fillEl.on = true;
84521             if (typeof params.fill == "string") {
84522                 fillUrl = params.fill.match(me.fillUrlRe);
84523                 if (fillUrl) {
84524                     fillUrl = fillUrl[1];
84525                     // If the URL matches one of the registered gradients, render that gradient
84526                     if (fillUrl.charAt(0) == "#") {
84527                         gradient = me.gradientsColl.getByKey(fillUrl.substring(1));
84528                     }
84529                     if (gradient) {
84530                         // VML angle is offset and inverted from standard, and must be adjusted to match rotation transform
84531                         rotation = params.rotation;
84532                         angle = -(gradient.angle + 270 + (rotation ? rotation.degrees : 0)) % 360;
84533                         // IE will flip the angle at 0 degrees...
84534                         if (angle === 0) {
84535                             angle = 180;
84536                         }
84537                         fillEl.angle = angle;
84538                         fillEl.type = "gradient";
84539                         fillEl.method = "sigma";
84540                         fillEl.colors = gradient.colors;
84541                     }
84542                     // Otherwise treat it as an image
84543                     else {
84544                         fillEl.src = fillUrl;
84545                         fillEl.type = "tile";
84546                         fillEl.rotate = true;
84547                     }
84548                 }
84549                 else {
84550                     fillEl.color = Ext.draw.Color.toHex(params.fill) || params.fill;
84551                     fillEl.src = "";
84552                     fillEl.type = "solid";
84553                 }
84554             }
84555         }
84556         dom.appendChild(fillEl);
84557     },
84558
84559     setStroke: function(sprite, params) {
84560         var me = this,
84561             el = sprite.el.dom,
84562             strokeEl = sprite.strokeEl,
84563             newStroke = false,
84564             width, opacity;
84565
84566         if (!strokeEl) {
84567             strokeEl = sprite.strokeEl = me.createNode("stroke");
84568             newStroke = true;
84569         }
84570         if (Ext.isArray(params.stroke)) {
84571             params.stroke = params.stroke[0];
84572         }
84573         if (!params.stroke || params.stroke == "none" || params.stroke == 0 || params["stroke-width"] == 0) {
84574             strokeEl.on = false;
84575         }
84576         else {
84577             strokeEl.on = true;
84578             if (params.stroke && !params.stroke.match(me.fillUrlRe)) {
84579                 // VML does NOT support a gradient stroke :(
84580                 strokeEl.color = Ext.draw.Color.toHex(params.stroke);
84581             }
84582             strokeEl.joinstyle = params["stroke-linejoin"];
84583             strokeEl.endcap = params["stroke-linecap"] || "round";
84584             strokeEl.miterlimit = params["stroke-miterlimit"] || 8;
84585             width = parseFloat(params["stroke-width"] || 1) * 0.75;
84586             opacity = params["stroke-opacity"] || 1;
84587             // VML Does not support stroke widths under 1, so we're going to fiddle with stroke-opacity instead.
84588             if (Ext.isNumber(width) && width < 1) {
84589                 strokeEl.weight = 1;
84590                 strokeEl.opacity = opacity * width;
84591             }
84592             else {
84593                 strokeEl.weight = width;
84594                 strokeEl.opacity = opacity;
84595             }
84596         }
84597         if (newStroke) {
84598             el.appendChild(strokeEl);
84599         }
84600     },
84601
84602     setClip: function(sprite, params) {
84603         var me = this,
84604             el = sprite.el,
84605             clipEl = sprite.clipEl,
84606             rect = String(params["clip-rect"]).split(me.separatorRe);
84607         if (!clipEl) {
84608             clipEl = sprite.clipEl = me.el.insertFirst(Ext.getDoc().dom.createElement("div"));
84609             clipEl.addCls(Ext.baseCSSPrefix + 'vml-sprite');
84610         }
84611         if (rect.length == 4) {
84612             rect[2] = +rect[2] + (+rect[0]);
84613             rect[3] = +rect[3] + (+rect[1]);
84614             clipEl.setStyle("clip", Ext.String.format("rect({1}px {2}px {3}px {0}px)", rect[0], rect[1], rect[2], rect[3]));
84615             clipEl.setSize(me.el.width, me.el.height);
84616         }
84617         else {
84618             clipEl.setStyle("clip", "");
84619         }
84620     },
84621
84622     setTextAttributes: function(sprite, params) {
84623         var me = this,
84624             vml = sprite.vml,
84625             textStyle = vml.textpath.style,
84626             spanCacheStyle = me.span.style,
84627             zoom = me.zoom,
84628             round = Math.round,
84629             fontObj = {
84630                 fontSize: "font-size",
84631                 fontWeight: "font-weight",
84632                 fontStyle: "font-style"
84633             },
84634             fontProp,
84635             paramProp;
84636         if (sprite.dirtyFont) {
84637             if (params.font) {
84638                 textStyle.font = spanCacheStyle.font = params.font;
84639             }
84640             if (params["font-family"]) {
84641                 textStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(me.fontFamilyRe, "") + '"';
84642                 spanCacheStyle.fontFamily = params["font-family"];
84643             }
84644
84645             for (fontProp in fontObj) {
84646                 paramProp = params[fontObj[fontProp]];
84647                 if (paramProp) {
84648                     textStyle[fontProp] = spanCacheStyle[fontProp] = paramProp;
84649                 }
84650             }
84651
84652             me.setText(sprite, params.text);
84653             
84654             if (vml.textpath.string) {
84655                 me.span.innerHTML = String(vml.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>");
84656             }
84657             vml.W = me.span.offsetWidth;
84658             vml.H = me.span.offsetHeight + 2; // TODO handle baseline differences and offset in VML Textpath
84659
84660             // text-anchor emulation
84661             if (params["text-anchor"] == "middle") {
84662                 textStyle["v-text-align"] = "center";
84663             }
84664             else if (params["text-anchor"] == "end") {
84665                 textStyle["v-text-align"] = "right";
84666                 vml.bbx = -Math.round(vml.W / 2);
84667             }
84668             else {
84669                 textStyle["v-text-align"] = "left";
84670                 vml.bbx = Math.round(vml.W / 2);
84671             }
84672         }
84673         vml.X = params.x;
84674         vml.Y = params.y;
84675         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);
84676         // Clear bbox cache
84677         sprite.bbox.plain = null;
84678         sprite.bbox.transform = null;
84679         sprite.dirtyFont = false;
84680     },
84681     
84682     setText: function(sprite, text) {
84683         sprite.vml.textpath.string = Ext.htmlDecode(text);
84684     },
84685
84686     hide: function() {
84687         this.el.hide();
84688     },
84689
84690     show: function() {
84691         this.el.show();
84692     },
84693
84694     hidePrim: function(sprite) {
84695         sprite.el.addCls(Ext.baseCSSPrefix + 'hide-visibility');
84696     },
84697
84698     showPrim: function(sprite) {
84699         sprite.el.removeCls(Ext.baseCSSPrefix + 'hide-visibility');
84700     },
84701
84702     setSize: function(width, height) {
84703         var me = this;
84704         width = width || me.width;
84705         height = height || me.height;
84706         me.width = width;
84707         me.height = height;
84708
84709         if (me.el) {
84710             // Size outer div
84711             if (width != undefined) {
84712                 me.el.setWidth(width);
84713             }
84714             if (height != undefined) {
84715                 me.el.setHeight(height);
84716             }
84717
84718             // Handle viewBox sizing
84719             me.applyViewBox();
84720
84721             me.callParent(arguments);
84722         }
84723     },
84724
84725     setViewBox: function(x, y, width, height) {
84726         this.callParent(arguments);
84727         this.viewBox = {
84728             x: x,
84729             y: y,
84730             width: width,
84731             height: height
84732         };
84733         this.applyViewBox();
84734     },
84735
84736     /**
84737      * @private Using the current viewBox property and the surface's width and height, calculate the
84738      * appropriate viewBoxShift that will be applied as a persistent transform to all sprites.
84739      */
84740     applyViewBox: function() {
84741         var me = this,
84742             viewBox = me.viewBox,
84743             width = me.width,
84744             height = me.height,
84745             viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
84746             relativeHeight, relativeWidth, size;
84747
84748         if (viewBox && (width || height)) {
84749             viewBoxX = viewBox.x;
84750             viewBoxY = viewBox.y;
84751             viewBoxWidth = viewBox.width;
84752             viewBoxHeight = viewBox.height;
84753             relativeHeight = height / viewBoxHeight;
84754             relativeWidth = width / viewBoxWidth;
84755
84756             if (viewBoxWidth * relativeHeight < width) {
84757                 viewBoxX -= (width - viewBoxWidth * relativeHeight) / 2 / relativeHeight;
84758             }
84759             if (viewBoxHeight * relativeWidth < height) {
84760                 viewBoxY -= (height - viewBoxHeight * relativeWidth) / 2 / relativeWidth;
84761             }
84762
84763             size = 1 / Math.max(viewBoxWidth / width, viewBoxHeight / height);
84764
84765             me.viewBoxShift = {
84766                 dx: -viewBoxX,
84767                 dy: -viewBoxY,
84768                 scale: size
84769             };
84770             me.items.each(function(item) {
84771                 me.transform(item);
84772             });
84773         }
84774     },
84775
84776     onAdd: function(item) {
84777         this.callParent(arguments);
84778         if (this.el) {
84779             this.renderItem(item);
84780         }
84781     },
84782
84783     onRemove: function(sprite) {
84784         if (sprite.el) {
84785             sprite.el.remove();
84786             delete sprite.el;
84787         }
84788         this.callParent(arguments);
84789     },
84790
84791     // VML Node factory method (createNode)
84792     createNode : (function () {
84793         try {
84794             var doc = Ext.getDoc().dom;
84795             if (!doc.namespaces.rvml) {
84796                 doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
84797             }
84798             return function (tagName) {
84799                 return doc.createElement("<rvml:" + tagName + ' class="rvml">');
84800             };
84801         } catch (e) {
84802             return function (tagName) {
84803                 return doc.createElement("<" + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
84804             };
84805         }
84806     })(),
84807
84808     render: function (container) {
84809         var me = this,
84810             doc = Ext.getDoc().dom;
84811
84812         if (!me.el) {
84813             var el = doc.createElement("div");
84814             me.el = Ext.get(el);
84815             me.el.addCls(me.baseVmlCls);
84816
84817             // Measuring span (offscrren)
84818             me.span = doc.createElement("span");
84819             Ext.get(me.span).addCls(me.measureSpanCls);
84820             el.appendChild(me.span);
84821             me.el.setSize(me.width || 10, me.height || 10);
84822             container.appendChild(el);
84823             me.el.on({
84824                 scope: me,
84825                 mouseup: me.onMouseUp,
84826                 mousedown: me.onMouseDown,
84827                 mouseover: me.onMouseOver,
84828                 mouseout: me.onMouseOut,
84829                 mousemove: me.onMouseMove,
84830                 mouseenter: me.onMouseEnter,
84831                 mouseleave: me.onMouseLeave,
84832                 click: me.onClick
84833             });
84834         }
84835         me.renderAll();
84836     },
84837
84838     renderAll: function() {
84839         this.items.each(this.renderItem, this);
84840     },
84841
84842     redraw: function(sprite) {
84843         sprite.dirty = true;
84844         this.renderItem(sprite);
84845     },
84846
84847     renderItem: function (sprite) {
84848         // Does the surface element exist?
84849         if (!this.el) {
84850             return;
84851         }
84852
84853         // Create sprite element if necessary
84854         if (!sprite.el) {
84855             this.createSpriteElement(sprite);
84856         }
84857
84858         if (sprite.dirty) {
84859             this.applyAttrs(sprite);
84860             if (sprite.dirtyTransform) {
84861                 this.applyTransformations(sprite);
84862             }
84863         }
84864     },
84865
84866     rotationCompensation: function (deg, dx, dy) {
84867         var matrix = Ext.create('Ext.draw.Matrix');
84868         matrix.rotate(-deg, 0.5, 0.5);
84869         return {
84870             x: matrix.x(dx, dy),
84871             y: matrix.y(dx, dy)
84872         };
84873     },
84874
84875     extractTransform: function (sprite) {
84876         var me = this,
84877             matrix = Ext.create('Ext.draw.Matrix'), scale,
84878             transformstions, tranformationsLength,
84879             transform, i = 0,
84880             shift = me.viewBoxShift;
84881
84882         for(transformstions = sprite.transformations, tranformationsLength = transformstions.length;
84883             i < tranformationsLength; i ++) {
84884             transform = transformstions[i];
84885             switch (transform.type) {
84886                 case 'translate' :
84887                     matrix.translate(transform.x, transform.y);
84888                     break;
84889                 case 'rotate':
84890                     matrix.rotate(transform.degrees, transform.x, transform.y);
84891                     break;
84892                 case 'scale':
84893                     matrix.scale(transform.x || transform.scale, transform.y || transform.scale, transform.centerX, transform.centerY);
84894                     break;
84895             }
84896         }
84897
84898         if (shift) {
84899             matrix.add(1, 0, 0, 1, shift.dx, shift.dy);
84900             matrix.prepend(shift.scale, 0, 0, shift.scale, 0, 0);
84901         }
84902         
84903         return sprite.matrix = matrix;
84904     },
84905
84906     setSimpleCoords: function(sprite, sx, sy, dx, dy, rotate) {
84907         var me = this,
84908             matrix = sprite.matrix,
84909             dom = sprite.el.dom,
84910             style = dom.style,
84911             yFlipper = 1,
84912             flip = "",
84913             fill = dom.getElementsByTagName('fill')[0],
84914             kx = me.zoom / sx,
84915             ky = me.zoom / sy,
84916             rotationCompensation;
84917         if (!sx || !sy) {
84918             return;
84919         }
84920         dom.coordsize = Math.abs(kx) + ' ' + Math.abs(ky);
84921         style.rotation = rotate * (sx * sy < 0 ? -1 : 1);
84922         if (rotate) {
84923             rotationCompensation = me.rotationCompensation(rotate, dx, dy);
84924             dx = rotationCompensation.x;
84925             dy = rotationCompensation.y;
84926         }
84927         if (sx < 0) {
84928             flip += "x"
84929         }
84930         if (sy < 0) {
84931             flip += " y";
84932             yFlipper = -1;
84933         }
84934         style.flip = flip;
84935         dom.coordorigin = (dx * -kx) + ' ' + (dy * -ky);
84936         if (fill) {
84937             dom.removeChild(fill);
84938             rotationCompensation = me.rotationCompensation(rotate, matrix.x(sprite.x, sprite.y), matrix.y(sprite.x, sprite.y));
84939             fill.position = rotationCompensation.x * yFlipper + ' ' + rotationCompensation.y * yFlipper;
84940             fill.size = sprite.width * Math.abs(sx) + ' ' + sprite.height * Math.abs(sy);
84941             dom.appendChild(fill);
84942         }
84943     },
84944
84945     transform : function (sprite) {
84946         var me = this,
84947             el = sprite.el,
84948             skew = sprite.skew,
84949             dom = el.dom,
84950             domStyle = dom.style,
84951             matrix = me.extractTransform(sprite).clone(),
84952             split, zoom = me.zoom,
84953             fill = dom.getElementsByTagName('fill')[0],
84954             isPatt = !String(sprite.fill).indexOf("url("),
84955             offset, c;
84956
84957
84958         // Hide element while we transform
84959
84960         if (sprite.type != "image" && skew && !isPatt) {
84961             // matrix transform via VML skew
84962             skew.matrix = matrix.toString();
84963             // skew.offset = '32767,1' OK
84964             // skew.offset = '32768,1' Crash
84965             // M$, R U kidding??
84966             offset = matrix.offset();
84967             if (offset[0] > 32767) {
84968                 offset[0] = 32767;
84969             } else if (offset[0] < -32768) {
84970                 offset[0] = -32768
84971             }
84972             if (offset[1] > 32767) {
84973                 offset[1] = 32767;
84974             } else if (offset[1] < -32768) {
84975                 offset[1] = -32768
84976             }
84977             skew.offset = offset;
84978         } else {
84979             if (skew) {
84980                 skew.matrix = "1 0 0 1";
84981                 skew.offset = "0 0";
84982             }
84983             split = matrix.split();
84984             if (split.isSimple) {
84985                 domStyle.filter = '';
84986                 me.setSimpleCoords(sprite, split.scaleX, split.scaleY, split.translateX, split.translateY, split.rotate / Math.PI * 180);
84987             } else {
84988                 domStyle.filter = matrix.toFilter();
84989                 var bb = me.getBBox(sprite),
84990                     dx = bb.x - sprite.x,
84991                     dy = bb.y - sprite.y;
84992                 dom.coordorigin = (dx * -zoom) + ' ' + (dy * -zoom);
84993                 if (fill) {
84994                     dom.removeChild(fill);
84995                     fill.position = dx + ' ' + dy;
84996                     fill.size = sprite.width * sprite.scale.x + ' ' + sprite.height * 1.1;
84997                     dom.appendChild(fill);
84998                 }
84999             }
85000         }
85001     },
85002
85003     createItem: function (config) {
85004         return Ext.create('Ext.draw.Sprite', config);
85005     },
85006
85007     getRegion: function() {
85008         return this.el.getRegion();
85009     },
85010
85011     addCls: function(sprite, className) {
85012         if (sprite && sprite.el) {
85013             sprite.el.addCls(className);
85014         }
85015     },
85016
85017     removeCls: function(sprite, className) {
85018         if (sprite && sprite.el) {
85019             sprite.el.removeCls(className);
85020         }
85021     },
85022
85023     /**
85024      * Adds a definition to this Surface for a linear gradient. We convert the gradient definition
85025      * to its corresponding VML attributes and store it for later use by individual sprites.
85026      * @param {Object} gradient
85027      */
85028     addGradient: function(gradient) {
85029         var gradients = this.gradientsColl || (this.gradientsColl = Ext.create('Ext.util.MixedCollection')),
85030             colors = [],
85031             stops = Ext.create('Ext.util.MixedCollection');
85032
85033         // Build colors string
85034         stops.addAll(gradient.stops);
85035         stops.sortByKey("ASC", function(a, b) {
85036             a = parseInt(a, 10);
85037             b = parseInt(b, 10);
85038             return a > b ? 1 : (a < b ? -1 : 0);
85039         });
85040         stops.eachKey(function(k, v) {
85041             colors.push(k + "% " + v.color);
85042         });
85043
85044         gradients.add(gradient.id, {
85045             colors: colors.join(","),
85046             angle: gradient.angle
85047         });
85048     },
85049
85050     destroy: function() {
85051         var me = this;
85052         
85053         me.callParent(arguments);
85054         if (me.el) {
85055             me.el.remove();
85056         }
85057         delete me.el;
85058     }
85059 });
85060
85061 /**
85062  * @class Ext.fx.target.ElementCSS
85063  * @extends Ext.fx.target.Element
85064  * 
85065  * This class represents a animation target for an {@link Ext.Element} that supports CSS
85066  * based animation. In general this class will not be created directly, the {@link Ext.Element} 
85067  * will be passed to the animation and the appropriate target will be created.
85068  */
85069 Ext.define('Ext.fx.target.ElementCSS', {
85070
85071     /* Begin Definitions */
85072
85073     extend: 'Ext.fx.target.Element',
85074
85075     /* End Definitions */
85076
85077     setAttr: function(targetData, isFirstFrame) {
85078         var cssArr = {
85079                 attrs: [],
85080                 duration: [],
85081                 easing: []
85082             },
85083             ln = targetData.length,
85084             attributes,
85085             attrs,
85086             attr,
85087             easing,
85088             duration,
85089             o,
85090             i,
85091             j,
85092             ln2;
85093         for (i = 0; i < ln; i++) {
85094             attrs = targetData[i];
85095             duration = attrs.duration;
85096             easing = attrs.easing;
85097             attrs = attrs.attrs;
85098             for (attr in attrs) {
85099                 if (Ext.Array.indexOf(cssArr.attrs, attr) == -1) {
85100                     cssArr.attrs.push(attr.replace(/[A-Z]/g, function(v) {
85101                         return '-' + v.toLowerCase();
85102                     }));
85103                     cssArr.duration.push(duration + 'ms');
85104                     cssArr.easing.push(easing);
85105                 }
85106             }
85107         }
85108         attributes = cssArr.attrs.join(',');
85109         duration = cssArr.duration.join(',');
85110         easing = cssArr.easing.join(', ');
85111         for (i = 0; i < ln; i++) {
85112             attrs = targetData[i].attrs;
85113             for (attr in attrs) {
85114                 ln2 = attrs[attr].length;
85115                 for (j = 0; j < ln2; j++) {
85116                     o = attrs[attr][j];
85117                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', isFirstFrame ? '' : attributes);
85118                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', isFirstFrame ? '' : duration);
85119                     o[0].setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', isFirstFrame ? '' : easing);
85120                     o[0].setStyle(attr, o[1]);
85121
85122                     // Must trigger reflow to make this get used as the start point for the transition that follows
85123                     if (isFirstFrame) {
85124                         o = o[0].dom.offsetWidth;
85125                     }
85126                     else {
85127                         // Remove transition properties when completed.
85128                         o[0].on(Ext.supports.CSS3TransitionEnd, function() {
85129                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionProperty', null);
85130                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionDuration', null);
85131                             this.setStyle(Ext.supports.CSS3Prefix + 'TransitionTimingFunction', null);
85132                         }, o[0], { single: true });
85133                     }
85134                 }
85135             }
85136         }
85137     }
85138 });
85139 /**
85140  * @class Ext.fx.target.CompositeElementCSS
85141  * @extends Ext.fx.target.CompositeElement
85142  * 
85143  * This class represents a animation target for a {@link Ext.CompositeElement}, where the
85144  * constituent elements support CSS based animation. It allows each {@link Ext.Element} in 
85145  * the group to be animated as a whole. In general this class will not be created directly, 
85146  * the {@link Ext.CompositeElement} will be passed to the animation and the appropriate target 
85147  * will be created.
85148  */
85149 Ext.define('Ext.fx.target.CompositeElementCSS', {
85150
85151     /* Begin Definitions */
85152
85153     extend: 'Ext.fx.target.CompositeElement',
85154
85155     requires: ['Ext.fx.target.ElementCSS'],
85156
85157     /* End Definitions */
85158     setAttr: function() {
85159         return Ext.fx.target.ElementCSS.prototype.setAttr.apply(this, arguments);
85160     }
85161 });
85162 /**
85163  * @class Ext.layout.container.AbstractFit
85164  * @extends Ext.layout.container.Container
85165  * @private
85166  */
85167 Ext.define('Ext.layout.container.AbstractFit', {
85168
85169     /* Begin Definitions */
85170
85171     extend: 'Ext.layout.container.Container',
85172
85173     /* End Definitions */
85174
85175     itemCls: Ext.baseCSSPrefix + 'fit-item',
85176     targetCls: Ext.baseCSSPrefix + 'layout-fit',
85177     type: 'fit'
85178 });
85179 /**
85180  * This is a base class for layouts that contain **a single item** that automatically expands to fill the layout's
85181  * container. This class is intended to be extended or created via the `layout: 'fit'`
85182  * {@link Ext.container.Container#layout} config, and should generally not need to be created directly via the new keyword.
85183  *
85184  * Fit layout does not have any direct config options (other than inherited ones). To fit a panel to a container using
85185  * Fit layout, simply set `layout: 'fit'` on the container and add a single panel to it. If the container has multiple
85186  * panels, only the first one will be displayed.
85187  *
85188  *     @example
85189  *     Ext.create('Ext.panel.Panel', {
85190  *         title: 'Fit Layout',
85191  *         width: 300,
85192  *         height: 150,
85193  *         layout:'fit',
85194  *         items: {
85195  *             title: 'Inner Panel',
85196  *             html: 'This is the inner panel content',
85197  *             bodyPadding: 20,
85198  *             border: false
85199  *         },
85200  *         renderTo: Ext.getBody()
85201  *     });
85202  */
85203 Ext.define('Ext.layout.container.Fit', {
85204
85205     /* Begin Definitions */
85206
85207     extend: 'Ext.layout.container.AbstractFit',
85208     alias: 'layout.fit',
85209     alternateClassName: 'Ext.layout.FitLayout',
85210     requires: ['Ext.layout.container.Box'],
85211
85212     /* End Definitions */
85213
85214     /**
85215      * @cfg {Object} defaultMargins
85216      * <p>If the individual contained items do not have a <tt>margins</tt>
85217      * property specified or margin specified via CSS, the default margins from this property will be
85218      * applied to each item.</p>
85219      * <br><p>This property may be specified as an object containing margins
85220      * to apply in the format:</p><pre><code>
85221 {
85222     top: (top margin),
85223     right: (right margin),
85224     bottom: (bottom margin),
85225     left: (left margin)
85226 }</code></pre>
85227      * <p>This property may also be specified as a string containing
85228      * space-separated, numeric margin values. The order of the sides associated
85229      * with each value matches the way CSS processes margin values:</p>
85230      * <div class="mdetail-params"><ul>
85231      * <li>If there is only one value, it applies to all sides.</li>
85232      * <li>If there are two values, the top and bottom borders are set to the
85233      * first value and the right and left are set to the second.</li>
85234      * <li>If there are three values, the top is set to the first value, the left
85235      * and right are set to the second, and the bottom is set to the third.</li>
85236      * <li>If there are four values, they apply to the top, right, bottom, and
85237      * left, respectively.</li>
85238      * </ul></div>
85239      * <p>Defaults to:</p><pre><code>
85240      * {top:0, right:0, bottom:0, left:0}
85241      * </code></pre>
85242      */
85243     defaultMargins: {
85244         top: 0,
85245         right: 0,
85246         bottom: 0,
85247         left: 0
85248     },
85249
85250     // @private
85251     onLayout : function() {
85252         var me = this,
85253             size,
85254             item,
85255             margins;
85256         me.callParent();
85257
85258         if (me.owner.items.length) {
85259             item = me.owner.items.get(0);
85260             margins = item.margins || me.defaultMargins;
85261             size = me.getLayoutTargetSize();
85262             size.width  -= margins.width;
85263             size.height -= margins.height;
85264             me.setItemBox(item, size);
85265
85266             // If any margins were configure either through the margins config, or in the CSS style,
85267             // Then positioning will be used.
85268             if (margins.left || margins.top) {
85269                 item.setPosition(margins.left, margins.top);
85270             }
85271         }
85272     },
85273
85274     getTargetBox : function() {
85275         return this.getLayoutTargetSize();
85276     },
85277
85278     setItemBox : function(item, box) {
85279         var me = this;
85280         if (item && box.height > 0) {
85281             if (!me.owner.isFixedWidth()) {
85282                box.width = undefined;
85283             }
85284             if (!me.owner.isFixedHeight()) {
85285                box.height = undefined;
85286             }
85287             me.setItemSize(item, box.width, box.height);
85288         }
85289     },
85290
85291     configureItem: function(item) {
85292
85293         // Card layout only controls dimensions which IT has controlled.
85294         // That calculation has to be determined at run time by examining the ownerCt's isFixedWidth()/isFixedHeight() methods
85295         item.layoutManagedHeight = 0;
85296         item.layoutManagedWidth = 0;
85297
85298         this.callParent(arguments);
85299     }
85300 }, function() {
85301     // Use Box layout's renderItem which reads CSS margins, and adds them to any configured item margins
85302     // (Defaulting to "0 0 0 0")
85303     this.prototype.renderItem = Ext.layout.container.Box.prototype.renderItem;
85304 });
85305 /**
85306  * Abstract base class for {@link Ext.layout.container.Card Card layout}.
85307  * @private
85308  */
85309 Ext.define('Ext.layout.container.AbstractCard', {
85310
85311     /* Begin Definitions */
85312
85313     extend: 'Ext.layout.container.Fit',
85314
85315     /* End Definitions */
85316
85317     type: 'card',
85318
85319     sizeAllCards: false,
85320
85321     hideInactive: true,
85322
85323     /**
85324      * @cfg {Boolean} deferredRender
85325      * True to render each contained item at the time it becomes active, false to render all contained items
85326      * as soon as the layout is rendered.  If there is a significant amount of content or
85327      * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
85328      * true might improve performance.
85329      */
85330     deferredRender : false,
85331
85332     beforeLayout: function() {
85333         var me = this;
85334         me.getActiveItem();
85335         if (me.activeItem && me.deferredRender) {
85336             me.renderItems([me.activeItem], me.getRenderTarget());
85337             return true;
85338         }
85339         else {
85340             return this.callParent(arguments);
85341         }
85342     },
85343
85344     renderChildren: function () {
85345         if (!this.deferredRender) {
85346             this.getActiveItem();
85347             this.callParent();
85348         }
85349     },
85350
85351     onLayout: function() {
85352         var me = this,
85353             activeItem = me.activeItem,
85354             items = me.getVisibleItems(),
85355             ln = items.length,
85356             targetBox = me.getTargetBox(),
85357             i, item;
85358
85359         for (i = 0; i < ln; i++) {
85360             item = items[i];
85361             me.setItemBox(item, targetBox);
85362         }
85363
85364         if (!me.firstActivated && activeItem) {
85365             if (activeItem.fireEvent('beforeactivate', activeItem) !== false) {
85366                 activeItem.fireEvent('activate', activeItem);
85367             }
85368             me.firstActivated = true;
85369         }
85370     },
85371
85372     isValidParent : function(item, target, position) {
85373         // Note: Card layout does not care about order within the target because only one is ever visible.
85374         // We only care whether the item is a direct child of the target.
85375         var itemEl = item.el ? item.el.dom : Ext.getDom(item);
85376         return (itemEl && itemEl.parentNode === (target.dom || target)) || false;
85377     },
85378
85379     /**
85380      * Return the active (visible) component in the layout.
85381      * @returns {Ext.Component}
85382      */
85383     getActiveItem: function() {
85384         var me = this;
85385         if (!me.activeItem && me.owner) {
85386             me.activeItem = me.parseActiveItem(me.owner.activeItem);
85387         }
85388
85389         if (me.activeItem && me.owner.items.indexOf(me.activeItem) != -1) {
85390             return me.activeItem;
85391         }
85392
85393         return null;
85394     },
85395
85396     // @private
85397     parseActiveItem: function(item) {
85398         if (item && item.isComponent) {
85399             return item;
85400         }
85401         else if (typeof item == 'number' || item === undefined) {
85402             return this.getLayoutItems()[item || 0];
85403         }
85404         else {
85405             return this.owner.getComponent(item);
85406         }
85407     },
85408
85409     // @private
85410     configureItem: function(item, position) {
85411         this.callParent([item, position]);
85412         if (this.hideInactive && this.activeItem !== item) {
85413             item.hide();
85414         }
85415         else {
85416             item.show();
85417         }
85418     },
85419
85420     onRemove: function(component) {
85421         if (component === this.activeItem) {
85422             this.activeItem = null;
85423             if (this.owner.items.getCount() === 0) {
85424                 this.firstActivated = false;
85425             }
85426         }
85427     },
85428
85429     // @private
85430     getAnimation: function(newCard, owner) {
85431         var newAnim = (newCard || {}).cardSwitchAnimation;
85432         if (newAnim === false) {
85433             return false;
85434         }
85435         return newAnim || owner.cardSwitchAnimation;
85436     },
85437
85438     /**
85439      * Return the active (visible) component in the layout to the next card
85440      * @returns {Ext.Component} The next component or false.
85441      */
85442     getNext: function() {
85443         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
85444         //should come back in 4.1
85445         var wrap = arguments[0];
85446         var items = this.getLayoutItems(),
85447             index = Ext.Array.indexOf(items, this.activeItem);
85448         return items[index + 1] || (wrap ? items[0] : false);
85449     },
85450
85451     /**
85452      * Sets the active (visible) component in the layout to the next card
85453      * @return {Ext.Component} the activated component or false when nothing activated.
85454      */
85455     next: function() {
85456         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
85457         //should come back in 4.1
85458         var anim = arguments[0], wrap = arguments[1];
85459         return this.setActiveItem(this.getNext(wrap), anim);
85460     },
85461
85462     /**
85463      * Return the active (visible) component in the layout to the previous card
85464      * @returns {Ext.Component} The previous component or false.
85465      */
85466     getPrev: function() {
85467         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
85468         //should come back in 4.1
85469         var wrap = arguments[0];
85470         var items = this.getLayoutItems(),
85471             index = Ext.Array.indexOf(items, this.activeItem);
85472         return items[index - 1] || (wrap ? items[items.length - 1] : false);
85473     },
85474
85475     /**
85476      * Sets the active (visible) component in the layout to the previous card
85477      * @return {Ext.Component} the activated component or false when nothing activated.
85478      */
85479     prev: function() {
85480         //NOTE: Removed the JSDoc for this function's arguments because it is not actually supported in 4.0. This
85481         //should come back in 4.1
85482         var anim = arguments[0], wrap = arguments[1];
85483         return this.setActiveItem(this.getPrev(wrap), anim);
85484     }
85485 });
85486
85487 /**
85488  * Tracks what records are currently selected in a databound component.
85489  *
85490  * This is an abstract class and is not meant to be directly used. Databound UI widgets such as
85491  * {@link Ext.grid.Panel Grid} and {@link Ext.tree.Panel Tree} should subclass Ext.selection.Model
85492  * and provide a way to binding to the component.
85493  *
85494  * The abstract methods `onSelectChange` and `onLastFocusChanged` should be implemented in these
85495  * subclasses to update the UI widget.
85496  */
85497 Ext.define('Ext.selection.Model', {
85498     extend: 'Ext.util.Observable',
85499     alternateClassName: 'Ext.AbstractSelectionModel',
85500     requires: ['Ext.data.StoreManager'],
85501     // lastSelected
85502
85503     /**
85504      * @cfg {String} mode
85505      * Mode of selection.  Valid values are:
85506      *
85507      * - **SINGLE** - Only allows selecting one item at a time.  Use {@link #allowDeselect} to allow
85508      *   deselecting that item.  This is the default.
85509      * - **SIMPLE** - Allows simple selection of multiple items one-by-one. Each click in grid will either
85510      *   select or deselect an item.
85511      * - **MULTI** - Allows complex selection of multiple items using Ctrl and Shift keys.
85512      */
85513
85514     /**
85515      * @cfg {Boolean} allowDeselect
85516      * Allow users to deselect a record in a DataView, List or Grid.
85517      * Only applicable when the {@link #mode} is 'SINGLE'.
85518      */
85519     allowDeselect: false,
85520
85521     /**
85522      * @property {Ext.util.MixedCollection} selected
85523      * A MixedCollection that maintains all of the currently selected records. Read-only.
85524      */
85525     selected: null,
85526
85527     /**
85528      * Prune records when they are removed from the store from the selection.
85529      * This is a private flag. For an example of its usage, take a look at
85530      * Ext.selection.TreeModel.
85531      * @private
85532      */
85533     pruneRemoved: true,
85534
85535     constructor: function(cfg) {
85536         var me = this;
85537
85538         cfg = cfg || {};
85539         Ext.apply(me, cfg);
85540
85541         me.addEvents(
85542             /**
85543              * @event
85544              * Fired after a selection change has occurred
85545              * @param {Ext.selection.Model} this
85546              * @param {Ext.data.Model[]} selected The selected records
85547              */
85548             'selectionchange'
85549         );
85550
85551         me.modes = {
85552             SINGLE: true,
85553             SIMPLE: true,
85554             MULTI: true
85555         };
85556
85557         // sets this.selectionMode
85558         me.setSelectionMode(cfg.mode || me.mode);
85559
85560         // maintains the currently selected records.
85561         me.selected = Ext.create('Ext.util.MixedCollection');
85562
85563         me.callParent(arguments);
85564     },
85565
85566     // binds the store to the selModel.
85567     bind : function(store, initial){
85568         var me = this;
85569
85570         if(!initial && me.store){
85571             if(store !== me.store && me.store.autoDestroy){
85572                 me.store.destroyStore();
85573             }else{
85574                 me.store.un("add", me.onStoreAdd, me);
85575                 me.store.un("clear", me.onStoreClear, me);
85576                 me.store.un("remove", me.onStoreRemove, me);
85577                 me.store.un("update", me.onStoreUpdate, me);
85578             }
85579         }
85580         if(store){
85581             store = Ext.data.StoreManager.lookup(store);
85582             store.on({
85583                 add: me.onStoreAdd,
85584                 clear: me.onStoreClear,
85585                 remove: me.onStoreRemove,
85586                 update: me.onStoreUpdate,
85587                 scope: me
85588             });
85589         }
85590         me.store = store;
85591         if(store && !initial) {
85592             me.refresh();
85593         }
85594     },
85595
85596     /**
85597      * Selects all records in the view.
85598      * @param {Boolean} suppressEvent True to suppress any select events
85599      */
85600     selectAll: function(suppressEvent) {
85601         var me = this,
85602             selections = me.store.getRange(),
85603             i = 0,
85604             len = selections.length,
85605             start = me.getSelection().length;
85606
85607         me.bulkChange = true;
85608         for (; i < len; i++) {
85609             me.doSelect(selections[i], true, suppressEvent);
85610         }
85611         delete me.bulkChange;
85612         // fire selection change only if the number of selections differs
85613         me.maybeFireSelectionChange(me.getSelection().length !== start);
85614     },
85615
85616     /**
85617      * Deselects all records in the view.
85618      * @param {Boolean} suppressEvent True to suppress any deselect events
85619      */
85620     deselectAll: function(suppressEvent) {
85621         var me = this,
85622             selections = me.getSelection(),
85623             i = 0,
85624             len = selections.length,
85625             start = me.getSelection().length;
85626
85627         me.bulkChange = true;
85628         for (; i < len; i++) {
85629             me.doDeselect(selections[i], suppressEvent);
85630         }
85631         delete me.bulkChange;
85632         // fire selection change only if the number of selections differs
85633         me.maybeFireSelectionChange(me.getSelection().length !== start);
85634     },
85635
85636     // Provides differentiation of logic between MULTI, SIMPLE and SINGLE
85637     // selection modes. Requires that an event be passed so that we can know
85638     // if user held ctrl or shift.
85639     selectWithEvent: function(record, e, keepExisting) {
85640         var me = this;
85641
85642         switch (me.selectionMode) {
85643             case 'MULTI':
85644                 if (e.ctrlKey && me.isSelected(record)) {
85645                     me.doDeselect(record, false);
85646                 } else if (e.shiftKey && me.lastFocused) {
85647                     me.selectRange(me.lastFocused, record, e.ctrlKey);
85648                 } else if (e.ctrlKey) {
85649                     me.doSelect(record, true, false);
85650                 } else if (me.isSelected(record) && !e.shiftKey && !e.ctrlKey && me.selected.getCount() > 1) {
85651                     me.doSelect(record, keepExisting, false);
85652                 } else {
85653                     me.doSelect(record, false);
85654                 }
85655                 break;
85656             case 'SIMPLE':
85657                 if (me.isSelected(record)) {
85658                     me.doDeselect(record);
85659                 } else {
85660                     me.doSelect(record, true);
85661                 }
85662                 break;
85663             case 'SINGLE':
85664                 // if allowDeselect is on and this record isSelected, deselect it
85665                 if (me.allowDeselect && me.isSelected(record)) {
85666                     me.doDeselect(record);
85667                 // select the record and do NOT maintain existing selections
85668                 } else {
85669                     me.doSelect(record, false);
85670                 }
85671                 break;
85672         }
85673     },
85674
85675     /**
85676      * Selects a range of rows if the selection model {@link #isLocked is not locked}.
85677      * All rows in between startRow and endRow are also selected.
85678      * @param {Ext.data.Model/Number} startRow The record or index of the first row in the range
85679      * @param {Ext.data.Model/Number} endRow The record or index of the last row in the range
85680      * @param {Boolean} [keepExisting] True to retain existing selections
85681      */
85682     selectRange : function(startRow, endRow, keepExisting, dir){
85683         var me = this,
85684             store = me.store,
85685             selectedCount = 0,
85686             i,
85687             tmp,
85688             dontDeselect,
85689             records = [];
85690
85691         if (me.isLocked()){
85692             return;
85693         }
85694
85695         if (!keepExisting) {
85696             me.deselectAll(true);
85697         }
85698
85699         if (!Ext.isNumber(startRow)) {
85700             startRow = store.indexOf(startRow);
85701         }
85702         if (!Ext.isNumber(endRow)) {
85703             endRow = store.indexOf(endRow);
85704         }
85705
85706         // swap values
85707         if (startRow > endRow){
85708             tmp = endRow;
85709             endRow = startRow;
85710             startRow = tmp;
85711         }
85712
85713         for (i = startRow; i <= endRow; i++) {
85714             if (me.isSelected(store.getAt(i))) {
85715                 selectedCount++;
85716             }
85717         }
85718
85719         if (!dir) {
85720             dontDeselect = -1;
85721         } else {
85722             dontDeselect = (dir == 'up') ? startRow : endRow;
85723         }
85724
85725         for (i = startRow; i <= endRow; i++){
85726             if (selectedCount == (endRow - startRow + 1)) {
85727                 if (i != dontDeselect) {
85728                     me.doDeselect(i, true);
85729                 }
85730             } else {
85731                 records.push(store.getAt(i));
85732             }
85733         }
85734         me.doMultiSelect(records, true);
85735     },
85736
85737     /**
85738      * Selects a record instance by record instance or index.
85739      * @param {Ext.data.Model[]/Number} records An array of records or an index
85740      * @param {Boolean} [keepExisting] True to retain existing selections
85741      * @param {Boolean} [suppressEvent] Set to true to not fire a select event
85742      */
85743     select: function(records, keepExisting, suppressEvent) {
85744         // Automatically selecting eg store.first() or store.last() will pass undefined, so that must just return;
85745         if (Ext.isDefined(records)) {
85746             this.doSelect(records, keepExisting, suppressEvent);
85747         }
85748     },
85749
85750     /**
85751      * Deselects a record instance by record instance or index.
85752      * @param {Ext.data.Model[]/Number} records An array of records or an index
85753      * @param {Boolean} [suppressEvent] Set to true to not fire a deselect event
85754      */
85755     deselect: function(records, suppressEvent) {
85756         this.doDeselect(records, suppressEvent);
85757     },
85758
85759     doSelect: function(records, keepExisting, suppressEvent) {
85760         var me = this,
85761             record;
85762
85763         if (me.locked) {
85764             return;
85765         }
85766         if (typeof records === "number") {
85767             records = [me.store.getAt(records)];
85768         }
85769         if (me.selectionMode == "SINGLE" && records) {
85770             record = records.length ? records[0] : records;
85771             me.doSingleSelect(record, suppressEvent);
85772         } else {
85773             me.doMultiSelect(records, keepExisting, suppressEvent);
85774         }
85775     },
85776
85777     doMultiSelect: function(records, keepExisting, suppressEvent) {
85778         var me = this,
85779             selected = me.selected,
85780             change = false,
85781             i = 0,
85782             len, record;
85783
85784         if (me.locked) {
85785             return;
85786         }
85787
85788
85789         records = !Ext.isArray(records) ? [records] : records;
85790         len = records.length;
85791         if (!keepExisting && selected.getCount() > 0) {
85792             if (me.doDeselect(me.getSelection(), suppressEvent) === false) {
85793                 return;
85794             }
85795             // TODO - coalesce the selectionchange event in deselect w/the one below...
85796         }
85797
85798         function commit () {
85799             selected.add(record);
85800             change = true;
85801         }
85802
85803         for (; i < len; i++) {
85804             record = records[i];
85805             if (keepExisting && me.isSelected(record)) {
85806                 continue;
85807             }
85808             me.lastSelected = record;
85809
85810             me.onSelectChange(record, true, suppressEvent, commit);
85811         }
85812         me.setLastFocused(record, suppressEvent);
85813         // fire selchange if there was a change and there is no suppressEvent flag
85814         me.maybeFireSelectionChange(change && !suppressEvent);
85815     },
85816
85817     // records can be an index, a record or an array of records
85818     doDeselect: function(records, suppressEvent) {
85819         var me = this,
85820             selected = me.selected,
85821             i = 0,
85822             len, record,
85823             attempted = 0,
85824             accepted = 0;
85825
85826         if (me.locked) {
85827             return false;
85828         }
85829
85830         if (typeof records === "number") {
85831             records = [me.store.getAt(records)];
85832         } else if (!Ext.isArray(records)) {
85833             records = [records];
85834         }
85835
85836         function commit () {
85837             ++accepted;
85838             selected.remove(record);
85839         }
85840
85841         len = records.length;
85842
85843         for (; i < len; i++) {
85844             record = records[i];
85845             if (me.isSelected(record)) {
85846                 if (me.lastSelected == record) {
85847                     me.lastSelected = selected.last();
85848                 }
85849                 ++attempted;
85850                 me.onSelectChange(record, false, suppressEvent, commit);
85851             }
85852         }
85853
85854         // fire selchange if there was a change and there is no suppressEvent flag
85855         me.maybeFireSelectionChange(accepted > 0 && !suppressEvent);
85856         return accepted === attempted;
85857     },
85858
85859     doSingleSelect: function(record, suppressEvent) {
85860         var me = this,
85861             changed = false,
85862             selected = me.selected;
85863
85864         if (me.locked) {
85865             return;
85866         }
85867         // already selected.
85868         // should we also check beforeselect?
85869         if (me.isSelected(record)) {
85870             return;
85871         }
85872
85873         function commit () {
85874             me.bulkChange = true;
85875             if (selected.getCount() > 0 && me.doDeselect(me.lastSelected, suppressEvent) === false) {
85876                 delete me.bulkChange;
85877                 return false;
85878             }
85879             delete me.bulkChange;
85880
85881             selected.add(record);
85882             me.lastSelected = record;
85883             changed = true;
85884         }
85885
85886         me.onSelectChange(record, true, suppressEvent, commit);
85887
85888         if (changed) {
85889             if (!suppressEvent) {
85890                 me.setLastFocused(record);
85891             }
85892             me.maybeFireSelectionChange(!suppressEvent);
85893         }
85894     },
85895
85896     /**
85897      * Sets a record as the last focused record. This does NOT mean
85898      * that the record has been selected.
85899      * @param {Ext.data.Model} record
85900      */
85901     setLastFocused: function(record, supressFocus) {
85902         var me = this,
85903             recordBeforeLast = me.lastFocused;
85904         me.lastFocused = record;
85905         me.onLastFocusChanged(recordBeforeLast, record, supressFocus);
85906     },
85907
85908     /**
85909      * Determines if this record is currently focused.
85910      * @param {Ext.data.Model} record
85911      */
85912     isFocused: function(record) {
85913         return record === this.getLastFocused();
85914     },
85915
85916
85917     // fire selection change as long as true is not passed
85918     // into maybeFireSelectionChange
85919     maybeFireSelectionChange: function(fireEvent) {
85920         var me = this;
85921         if (fireEvent && !me.bulkChange) {
85922             me.fireEvent('selectionchange', me, me.getSelection());
85923         }
85924     },
85925
85926     /**
85927      * Returns the last selected record.
85928      */
85929     getLastSelected: function() {
85930         return this.lastSelected;
85931     },
85932
85933     getLastFocused: function() {
85934         return this.lastFocused;
85935     },
85936
85937     /**
85938      * Returns an array of the currently selected records.
85939      * @return {Ext.data.Model[]} The selected records
85940      */
85941     getSelection: function() {
85942         return this.selected.getRange();
85943     },
85944
85945     /**
85946      * Returns the current selectionMode.
85947      * @return {String} The selectionMode: 'SINGLE', 'MULTI' or 'SIMPLE'.
85948      */
85949     getSelectionMode: function() {
85950         return this.selectionMode;
85951     },
85952
85953     /**
85954      * Sets the current selectionMode.
85955      * @param {String} selModel 'SINGLE', 'MULTI' or 'SIMPLE'.
85956      */
85957     setSelectionMode: function(selMode) {
85958         selMode = selMode ? selMode.toUpperCase() : 'SINGLE';
85959         // set to mode specified unless it doesnt exist, in that case
85960         // use single.
85961         this.selectionMode = this.modes[selMode] ? selMode : 'SINGLE';
85962     },
85963
85964     /**
85965      * Returns true if the selections are locked.
85966      * @return {Boolean}
85967      */
85968     isLocked: function() {
85969         return this.locked;
85970     },
85971
85972     /**
85973      * Locks the current selection and disables any changes from happening to the selection.
85974      * @param {Boolean} locked  True to lock, false to unlock.
85975      */
85976     setLocked: function(locked) {
85977         this.locked = !!locked;
85978     },
85979
85980     /**
85981      * Returns true if the specified row is selected.
85982      * @param {Ext.data.Model/Number} record The record or index of the record to check
85983      * @return {Boolean}
85984      */
85985     isSelected: function(record) {
85986         record = Ext.isNumber(record) ? this.store.getAt(record) : record;
85987         return this.selected.indexOf(record) !== -1;
85988     },
85989
85990     /**
85991      * Returns true if there are any a selected records.
85992      * @return {Boolean}
85993      */
85994     hasSelection: function() {
85995         return this.selected.getCount() > 0;
85996     },
85997
85998     refresh: function() {
85999         var me = this,
86000             toBeSelected = [],
86001             oldSelections = me.getSelection(),
86002             len = oldSelections.length,
86003             selection,
86004             change,
86005             i = 0,
86006             lastFocused = this.getLastFocused();
86007
86008         // check to make sure that there are no records
86009         // missing after the refresh was triggered, prune
86010         // them from what is to be selected if so
86011         for (; i < len; i++) {
86012             selection = oldSelections[i];
86013             if (!this.pruneRemoved || me.store.indexOf(selection) !== -1) {
86014                 toBeSelected.push(selection);
86015             }
86016         }
86017
86018         // there was a change from the old selected and
86019         // the new selection
86020         if (me.selected.getCount() != toBeSelected.length) {
86021             change = true;
86022         }
86023
86024         me.clearSelections();
86025
86026         if (me.store.indexOf(lastFocused) !== -1) {
86027             // restore the last focus but supress restoring focus
86028             this.setLastFocused(lastFocused, true);
86029         }
86030
86031         if (toBeSelected.length) {
86032             // perform the selection again
86033             me.doSelect(toBeSelected, false, true);
86034         }
86035
86036         me.maybeFireSelectionChange(change);
86037     },
86038
86039     /**
86040      * A fast reset of the selections without firing events, updating the ui, etc.
86041      * For private usage only.
86042      * @private
86043      */
86044     clearSelections: function() {
86045         // reset the entire selection to nothing
86046         this.selected.clear();
86047         this.lastSelected = null;
86048         this.setLastFocused(null);
86049     },
86050
86051     // when a record is added to a store
86052     onStoreAdd: function() {
86053
86054     },
86055
86056     // when a store is cleared remove all selections
86057     // (if there were any)
86058     onStoreClear: function() {
86059         if (this.selected.getCount > 0) {
86060             this.clearSelections();
86061             this.maybeFireSelectionChange(true);
86062         }
86063     },
86064
86065     // prune records from the SelectionModel if
86066     // they were selected at the time they were
86067     // removed.
86068     onStoreRemove: function(store, record) {
86069         var me = this,
86070             selected = me.selected;
86071
86072         if (me.locked || !me.pruneRemoved) {
86073             return;
86074         }
86075
86076         if (selected.remove(record)) {
86077             if (me.lastSelected == record) {
86078                 me.lastSelected = null;
86079             }
86080             if (me.getLastFocused() == record) {
86081                 me.setLastFocused(null);
86082             }
86083             me.maybeFireSelectionChange(true);
86084         }
86085     },
86086
86087     /**
86088      * Returns the count of selected records.
86089      * @return {Number} The number of selected records
86090      */
86091     getCount: function() {
86092         return this.selected.getCount();
86093     },
86094
86095     // cleanup.
86096     destroy: function() {
86097
86098     },
86099
86100     // if records are updated
86101     onStoreUpdate: function() {
86102
86103     },
86104
86105     // @abstract
86106     onSelectChange: function(record, isSelected, suppressEvent) {
86107
86108     },
86109
86110     // @abstract
86111     onLastFocusChanged: function(oldFocused, newFocused) {
86112
86113     },
86114
86115     // @abstract
86116     onEditorKey: function(field, e) {
86117
86118     },
86119
86120     // @abstract
86121     bindComponent: function(cmp) {
86122
86123     }
86124 });
86125 /**
86126  * @class Ext.selection.DataViewModel
86127  * @ignore
86128  */
86129 Ext.define('Ext.selection.DataViewModel', {
86130     extend: 'Ext.selection.Model',
86131
86132     requires: ['Ext.util.KeyNav'],
86133
86134     deselectOnContainerClick: true,
86135
86136     /**
86137      * @cfg {Boolean} enableKeyNav
86138      *
86139      * Turns on/off keyboard navigation within the DataView.
86140      */
86141     enableKeyNav: true,
86142
86143     constructor: function(cfg){
86144         this.addEvents(
86145             /**
86146              * @event beforedeselect
86147              * Fired before a record is deselected. If any listener returns false, the
86148              * deselection is cancelled.
86149              * @param {Ext.selection.DataViewModel} this
86150              * @param {Ext.data.Model} record The deselected record
86151              */
86152             'beforedeselect',
86153
86154             /**
86155              * @event beforeselect
86156              * Fired before a record is selected. If any listener returns false, the
86157              * selection is cancelled.
86158              * @param {Ext.selection.DataViewModel} this
86159              * @param {Ext.data.Model} record The selected record
86160              */
86161             'beforeselect',
86162
86163             /**
86164              * @event deselect
86165              * Fired after a record is deselected
86166              * @param {Ext.selection.DataViewModel} this
86167              * @param  {Ext.data.Model} record The deselected record
86168              */
86169             'deselect',
86170
86171             /**
86172              * @event select
86173              * Fired after a record is selected
86174              * @param {Ext.selection.DataViewModel} this
86175              * @param  {Ext.data.Model} record The selected record
86176              */
86177             'select'
86178         );
86179         this.callParent(arguments);
86180     },
86181
86182     bindComponent: function(view) {
86183         var me = this,
86184             eventListeners = {
86185                 refresh: me.refresh,
86186                 scope: me
86187             };
86188
86189         me.view = view;
86190         me.bind(view.getStore());
86191
86192         view.on(view.triggerEvent, me.onItemClick, me);
86193         view.on(view.triggerCtEvent, me.onContainerClick, me);
86194
86195         view.on(eventListeners);
86196
86197         if (me.enableKeyNav) {
86198             me.initKeyNav(view);
86199         }
86200     },
86201
86202     onItemClick: function(view, record, item, index, e) {
86203         this.selectWithEvent(record, e);
86204     },
86205
86206     onContainerClick: function() {
86207         if (this.deselectOnContainerClick) {
86208             this.deselectAll();
86209         }
86210     },
86211
86212     initKeyNav: function(view) {
86213         var me = this;
86214
86215         if (!view.rendered) {
86216             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
86217             return;
86218         }
86219
86220         view.el.set({
86221             tabIndex: -1
86222         });
86223         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
86224             down: Ext.pass(me.onNavKey, [1], me),
86225             right: Ext.pass(me.onNavKey, [1], me),
86226             left: Ext.pass(me.onNavKey, [-1], me),
86227             up: Ext.pass(me.onNavKey, [-1], me),
86228             scope: me
86229         });
86230     },
86231
86232     onNavKey: function(step) {
86233         step = step || 1;
86234         var me = this,
86235             view = me.view,
86236             selected = me.getSelection()[0],
86237             numRecords = me.view.store.getCount(),
86238             idx;
86239
86240         if (selected) {
86241             idx = view.indexOf(view.getNode(selected)) + step;
86242         } else {
86243             idx = 0;
86244         }
86245
86246         if (idx < 0) {
86247             idx = numRecords - 1;
86248         } else if (idx >= numRecords) {
86249             idx = 0;
86250         }
86251
86252         me.select(idx);
86253     },
86254
86255     // Allow the DataView to update the ui
86256     onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
86257         var me = this,
86258             view = me.view,
86259             eventName = isSelected ? 'select' : 'deselect';
86260
86261         if ((suppressEvent || me.fireEvent('before' + eventName, me, record)) !== false &&
86262                 commitFn() !== false) {
86263
86264             if (isSelected) {
86265                 view.onItemSelect(record);
86266             } else {
86267                 view.onItemDeselect(record);
86268             }
86269
86270             if (!suppressEvent) {
86271                 me.fireEvent(eventName, me, record);
86272             }
86273         }
86274     },
86275     
86276     destroy: function(){
86277         Ext.destroy(this.keyNav);
86278         this.callParent();
86279     }
86280 });
86281
86282 /**
86283  * A Provider implementation which saves and retrieves state via cookies. The CookieProvider supports the usual cookie
86284  * options, such as:
86285  *
86286  * - {@link #path}
86287  * - {@link #expires}
86288  * - {@link #domain}
86289  * - {@link #secure}
86290  *
86291  * Example:
86292  *
86293  *     Ext.create('Ext.state.CookieProvider', {
86294  *         path: "/cgi-bin/",
86295  *         expires: new Date(new Date().getTime()+(1000*60*60*24*30)), //30 days
86296  *         domain: "sencha.com"
86297  *     });
86298  *
86299  *     Ext.state.Manager.setProvider(cp);
86300  *
86301  * @constructor
86302  * Creates a new CookieProvider.
86303  * @param {Object} config (optional) Config object.
86304  * @return {Object}
86305  */
86306 Ext.define('Ext.state.CookieProvider', {
86307     extend: 'Ext.state.Provider',
86308
86309     /**
86310      * @cfg {String} path
86311      * The path for which the cookie is active. Defaults to root '/' which makes it active for all pages in the site.
86312      */
86313
86314     /**
86315      * @cfg {Date} expires
86316      * The cookie expiration date. Defaults to 7 days from now.
86317      */
86318
86319     /**
86320      * @cfg {String} domain
86321      * The domain to save the cookie for. Note that you cannot specify a different domain than your page is on, but you can
86322      * specify a sub-domain, or simply the domain itself like 'sencha.com' to include all sub-domains if you need to access
86323      * cookies across different sub-domains. Defaults to null which uses the same domain the page is running on including
86324      * the 'www' like 'www.sencha.com'.
86325      */
86326
86327     /**
86328      * @cfg {Boolean} [secure=false]
86329      * True if the site is using SSL
86330      */
86331
86332     /**
86333      * Creates a new CookieProvider.
86334      * @param {Object} [config] Config object.
86335      */
86336     constructor : function(config){
86337         var me = this;
86338         me.path = "/";
86339         me.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
86340         me.domain = null;
86341         me.secure = false;
86342         me.callParent(arguments);
86343         me.state = me.readCookies();
86344     },
86345
86346     // private
86347     set : function(name, value){
86348         var me = this;
86349
86350         if(typeof value == "undefined" || value === null){
86351             me.clear(name);
86352             return;
86353         }
86354         me.setCookie(name, value);
86355         me.callParent(arguments);
86356     },
86357
86358     // private
86359     clear : function(name){
86360         this.clearCookie(name);
86361         this.callParent(arguments);
86362     },
86363
86364     // private
86365     readCookies : function(){
86366         var cookies = {},
86367             c = document.cookie + ";",
86368             re = /\s?(.*?)=(.*?);/g,
86369             prefix = this.prefix,
86370             len = prefix.length,
86371             matches,
86372             name,
86373             value;
86374
86375         while((matches = re.exec(c)) != null){
86376             name = matches[1];
86377             value = matches[2];
86378             if (name && name.substring(0, len) == prefix){
86379                 cookies[name.substr(len)] = this.decodeValue(value);
86380             }
86381         }
86382         return cookies;
86383     },
86384
86385     // private
86386     setCookie : function(name, value){
86387         var me = this;
86388
86389         document.cookie = me.prefix + name + "=" + me.encodeValue(value) +
86390            ((me.expires == null) ? "" : ("; expires=" + me.expires.toGMTString())) +
86391            ((me.path == null) ? "" : ("; path=" + me.path)) +
86392            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
86393            ((me.secure == true) ? "; secure" : "");
86394     },
86395
86396     // private
86397     clearCookie : function(name){
86398         var me = this;
86399
86400         document.cookie = me.prefix + name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" +
86401            ((me.path == null) ? "" : ("; path=" + me.path)) +
86402            ((me.domain == null) ? "" : ("; domain=" + me.domain)) +
86403            ((me.secure == true) ? "; secure" : "");
86404     }
86405 });
86406
86407 /**
86408  * @class Ext.state.LocalStorageProvider
86409  * @extends Ext.state.Provider
86410  * A Provider implementation which saves and retrieves state via the HTML5 localStorage object.
86411  * If the browser does not support local storage, an exception will be thrown upon instantiating
86412  * this class.
86413  */
86414
86415 Ext.define('Ext.state.LocalStorageProvider', {
86416     /* Begin Definitions */
86417     
86418     extend: 'Ext.state.Provider',
86419     
86420     alias: 'state.localstorage',
86421     
86422     /* End Definitions */
86423    
86424     constructor: function(){
86425         var me = this;
86426         me.callParent(arguments);
86427         me.store = me.getStorageObject();
86428         me.state = me.readLocalStorage();
86429     },
86430     
86431     readLocalStorage: function(){
86432         var store = this.store,
86433             i = 0,
86434             len = store.length,
86435             prefix = this.prefix,
86436             prefixLen = prefix.length,
86437             data = {},
86438             key;
86439             
86440         for (; i < len; ++i) {
86441             key = store.key(i);
86442             if (key.substring(0, prefixLen) == prefix) {
86443                 data[key.substr(prefixLen)] = this.decodeValue(store.getItem(key));
86444             }            
86445         }
86446         return data;
86447     },
86448     
86449     set : function(name, value){
86450         var me = this;
86451         
86452         me.clear(name);
86453         if (typeof value == "undefined" || value === null) {
86454             return;
86455         }
86456         me.store.setItem(me.prefix + name, me.encodeValue(value));
86457         me.callParent(arguments);
86458     },
86459
86460     // private
86461     clear : function(name){
86462         this.store.removeItem(this.prefix + name);
86463         this.callParent(arguments);
86464     },
86465     
86466     getStorageObject: function(){
86467         try {
86468             var supports = 'localStorage' in window && window['localStorage'] !== null;
86469             if (supports) {
86470                 return window.localStorage;
86471             }
86472         } catch (e) {
86473             return false;
86474         }
86475     }    
86476 });
86477
86478 /**
86479  * Represents a 2D point with x and y properties, useful for comparison and instantiation
86480  * from an event:
86481  *
86482  *     var point = Ext.util.Point.fromEvent(e);
86483  *
86484  */
86485 Ext.define('Ext.util.Point', {
86486
86487     /* Begin Definitions */
86488     extend: 'Ext.util.Region',
86489
86490     statics: {
86491
86492         /**
86493          * Returns a new instance of Ext.util.Point base on the pageX / pageY values of the given event
86494          * @static
86495          * @param {Event} e The event
86496          * @return {Ext.util.Point}
86497          */
86498         fromEvent: function(e) {
86499             e = (e.changedTouches && e.changedTouches.length > 0) ? e.changedTouches[0] : e;
86500             return new this(e.pageX, e.pageY);
86501         }
86502     },
86503
86504     /* End Definitions */
86505
86506     /**
86507      * Creates a point from two coordinates.
86508      * @param {Number} x X coordinate.
86509      * @param {Number} y Y coordinate.
86510      */
86511     constructor: function(x, y) {
86512         this.callParent([y, x, y, x]);
86513     },
86514
86515     /**
86516      * Returns a human-eye-friendly string that represents this point,
86517      * useful for debugging
86518      * @return {String}
86519      */
86520     toString: function() {
86521         return "Point[" + this.x + "," + this.y + "]";
86522     },
86523
86524     /**
86525      * Compare this point and another point
86526      * @param {Ext.util.Point/Object} The point to compare with, either an instance
86527      * of Ext.util.Point or an object with left and top properties
86528      * @return {Boolean} Returns whether they are equivalent
86529      */
86530     equals: function(p) {
86531         return (this.x == p.x && this.y == p.y);
86532     },
86533
86534     /**
86535      * Whether the given point is not away from this point within the given threshold amount.
86536      * @param {Ext.util.Point/Object} p The point to check with, either an instance
86537      * of Ext.util.Point or an object with left and top properties
86538      * @param {Object/Number} threshold Can be either an object with x and y properties or a number
86539      * @return {Boolean}
86540      */
86541     isWithin: function(p, threshold) {
86542         if (!Ext.isObject(threshold)) {
86543             threshold = {
86544                 x: threshold,
86545                 y: threshold
86546             };
86547         }
86548
86549         return (this.x <= p.x + threshold.x && this.x >= p.x - threshold.x &&
86550                 this.y <= p.y + threshold.y && this.y >= p.y - threshold.y);
86551     },
86552
86553     /**
86554      * Compare this point with another point when the x and y values of both points are rounded. E.g:
86555      * [100.3,199.8] will equals to [100, 200]
86556      * @param {Ext.util.Point/Object} p The point to compare with, either an instance
86557      * of Ext.util.Point or an object with x and y properties
86558      * @return {Boolean}
86559      */
86560     roundedEquals: function(p) {
86561         return (Math.round(this.x) == Math.round(p.x) && Math.round(this.y) == Math.round(p.y));
86562     }
86563 }, function() {
86564     /**
86565      * @method
86566      * Alias for {@link #translateBy}
86567      * @alias Ext.util.Region#translateBy
86568      */
86569     this.prototype.translate = Ext.util.Region.prototype.translateBy;
86570 });
86571
86572 /**
86573  * @class Ext.LoadMask
86574  * <p>A modal, floating Component which may be shown above a specified {@link Ext.core.Element Element}, or a specified
86575  * {@link Ext.Component Component} while loading data. When shown, the configured owning Element or Component will
86576  * be covered with a modality mask, and the LoadMask's {@link #msg} will be displayed centered, accompanied by a spinner image.</p>
86577  * <p>If the {@link #store} config option is specified, the masking will be automatically shown and then hidden synchronized with
86578  * the Store's loading process.</p>
86579  * <p>Because this is a floating Component, its z-index will be managed by the global {@link Ext.WindowManager ZIndexManager}
86580  * object, and upon show, it will place itsef at the top of the hierarchy.</p>
86581  * <p>Example usage:</p>
86582  * <pre><code>
86583 // Basic mask:
86584 var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
86585 myMask.show();
86586 </code></pre>
86587
86588  */
86589
86590 Ext.define('Ext.LoadMask', {
86591
86592     extend: 'Ext.Component',
86593
86594     alias: 'widget.loadmask',
86595
86596     /* Begin Definitions */
86597
86598     mixins: {
86599         floating: 'Ext.util.Floating'
86600     },
86601
86602     uses: ['Ext.data.StoreManager'],
86603
86604     /* End Definitions */
86605
86606     /**
86607      * @cfg {Ext.data.Store} store
86608      * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
86609      * hidden on either load success, or load fail.
86610      */
86611
86612     /**
86613      * @cfg {String} msg
86614      * The text to display in a centered loading message box.
86615      */
86616     msg : 'Loading...',
86617     /**
86618      * @cfg {String} [msgCls="x-mask-loading"]
86619      * The CSS class to apply to the loading message element.
86620      */
86621     msgCls : Ext.baseCSSPrefix + 'mask-loading',
86622     
86623     /**
86624      * @cfg {Boolean} useMsg
86625      * Whether or not to use a loading message class or simply mask the bound element.
86626      */
86627     useMsg: true,
86628
86629     /**
86630      * Read-only. True if the mask is currently disabled so that it will not be displayed
86631      * @type Boolean
86632      */
86633     disabled: false,
86634
86635     baseCls: Ext.baseCSSPrefix + 'mask-msg',
86636
86637     renderTpl: '<div style="position:relative" class="{msgCls}"></div>',
86638
86639     // Private. The whole point is that there's a mask.
86640     modal: true,
86641
86642     // Private. Obviously, it's floating.
86643     floating: {
86644         shadow: 'frame'
86645     },
86646
86647     // Private. Masks are not focusable
86648     focusOnToFront: false,
86649
86650     /**
86651      * Creates new LoadMask.
86652      * @param {String/HTMLElement/Ext.Element} el The element, element ID, or DOM node you wish to mask.
86653      * <p>Also, may be a {@link Ext.Component Component} who's element you wish to mask. If a Component is specified, then
86654      * the mask will be automatically sized upon Component resize, the message box will be kept centered,
86655      * and the mask only be visible when the Component is.</p>
86656      * @param {Object} [config] The config object
86657      */
86658     constructor : function(el, config) {
86659         var me = this;
86660
86661         // If a Component passed, bind to it.
86662         if (el.isComponent) {
86663             me.ownerCt = el;
86664             me.bindComponent(el);
86665         }
86666         // Create a dumy Component encapsulating the specified Element
86667         else {
86668             me.ownerCt = new Ext.Component({
86669                 el: Ext.get(el),
86670                 rendered: true,
86671                 componentLayoutCounter: 1
86672             });
86673             me.container = el;
86674         }
86675         me.callParent([config]);
86676
86677         if (me.store) {
86678             me.bindStore(me.store, true);
86679         }
86680         me.renderData = {
86681             msgCls: me.msgCls
86682         };
86683         me.renderSelectors = {
86684             msgEl: 'div'
86685         };
86686     },
86687
86688     bindComponent: function(comp) {
86689         this.mon(comp, {
86690             resize: this.onComponentResize,
86691             scope: this
86692         });
86693     },
86694
86695     afterRender: function() {
86696         this.callParent(arguments);
86697         this.container = this.floatParent.getContentTarget();
86698     },
86699
86700     /**
86701      * @private
86702      * Called when this LoadMask's Component is resized. The toFront method rebases and resizes the modal mask.
86703      */
86704     onComponentResize: function() {
86705         var me = this;
86706         if (me.rendered && me.isVisible()) {
86707             me.toFront();
86708             me.center();
86709         }
86710     },
86711
86712     /**
86713      * Changes the data store bound to this LoadMask.
86714      * @param {Ext.data.Store} store The store to bind to this LoadMask
86715      */
86716     bindStore : function(store, initial) {
86717         var me = this;
86718
86719         if (!initial && me.store) {
86720             me.mun(me.store, {
86721                 scope: me,
86722                 beforeload: me.onBeforeLoad,
86723                 load: me.onLoad,
86724                 exception: me.onLoad
86725             });
86726             if (!store) {
86727                 me.store = null;
86728             }
86729         }
86730         if (store) {
86731             store = Ext.data.StoreManager.lookup(store);
86732             me.mon(store, {
86733                 scope: me,
86734                 beforeload: me.onBeforeLoad,
86735                 load: me.onLoad,
86736                 exception: me.onLoad
86737             });
86738
86739         }
86740         me.store = store;
86741         if (store && store.isLoading()) {
86742             me.onBeforeLoad();
86743         }
86744     },
86745
86746     onDisable : function() {
86747         this.callParent(arguments);
86748         if (this.loading) {
86749             this.onLoad();
86750         }
86751     },
86752
86753     // private
86754     onBeforeLoad : function() {
86755         var me = this,
86756             owner = me.ownerCt || me.floatParent,
86757             origin;
86758         if (!this.disabled) {
86759             // If the owning Component has not been layed out, defer so that the ZIndexManager
86760             // gets to read its layed out size when sizing the modal mask
86761             if (owner.componentLayoutCounter) {
86762                 Ext.Component.prototype.show.call(me);
86763             } else {
86764                 // The code below is a 'run-once' interceptor.
86765                 origin = owner.afterComponentLayout;
86766                 owner.afterComponentLayout = function() {
86767                     owner.afterComponentLayout = origin;
86768                     origin.apply(owner, arguments);
86769                     if(me.loading) {
86770                         Ext.Component.prototype.show.call(me);
86771                     }
86772                 };
86773             }
86774         }
86775     },
86776
86777     onHide: function(){
86778         var me = this;
86779         me.callParent(arguments);
86780         me.showOnParentShow = true;
86781     },
86782
86783     onShow: function() {
86784         var me = this,
86785             msgEl = me.msgEl;
86786             
86787         me.callParent(arguments);
86788         me.loading = true;
86789         if (me.useMsg) {
86790             msgEl.show().update(me.msg);
86791         } else {
86792             msgEl.parent().hide();
86793         }
86794     },
86795
86796     afterShow: function() {
86797         this.callParent(arguments);
86798         this.center();
86799     },
86800
86801     // private
86802     onLoad : function() {
86803         this.loading = false;
86804         Ext.Component.prototype.hide.call(this);
86805     }
86806 });
86807 /**
86808  * @class Ext.view.AbstractView
86809  * @extends Ext.Component
86810  * This is an abstract superclass and should not be used directly. Please see {@link Ext.view.View}.
86811  * @private
86812  */
86813 Ext.define('Ext.view.AbstractView', {
86814     extend: 'Ext.Component',
86815     alternateClassName: 'Ext.view.AbstractView',
86816     requires: [
86817         'Ext.LoadMask',
86818         'Ext.data.StoreManager',
86819         'Ext.CompositeElementLite',
86820         'Ext.DomQuery',
86821         'Ext.selection.DataViewModel'
86822     ],
86823
86824     inheritableStatics: {
86825         getRecord: function(node) {
86826             return this.getBoundView(node).getRecord(node);
86827         },
86828
86829         getBoundView: function(node) {
86830             return Ext.getCmp(node.boundView);
86831         }
86832     },
86833
86834     /**
86835      * @cfg {String/String[]/Ext.XTemplate} tpl (required)
86836      * The HTML fragment or an array of fragments that will make up the template used by this DataView.  This should
86837      * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
86838      */
86839     /**
86840      * @cfg {Ext.data.Store} store (required)
86841      * The {@link Ext.data.Store} to bind this DataView to.
86842      */
86843
86844     /**
86845      * @cfg {Boolean} deferInitialRefresh
86846      * <p>Defaults to <code>true</code> to defer the initial refresh of the view.</p>
86847      * <p>This allows the View to execute its render and initial layout more quickly because the process will not be encumbered
86848      * by the expensive update of the view structure.</p>
86849      * <p><b>Important: </b>Be aware that this will mean that the View's item elements will not be available immediately upon render, so
86850      * <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.
86851      * Or set <code>deferInitialrefresh</code> to false, but this will be at the cost of slower rendering.</p>
86852      */
86853     deferInitialRefresh: true,
86854
86855     /**
86856      * @cfg {String} itemSelector (required)
86857      * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
86858      * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be
86859      * working with. The itemSelector is used to map DOM nodes to records. As such, there should
86860      * only be one root level element that matches the selector for each record.
86861      */
86862
86863     /**
86864      * @cfg {String} itemCls
86865      * Specifies the class to be assigned to each element in the view when used in conjunction with the
86866      * {@link #itemTpl} configuration.
86867      */
86868     itemCls: Ext.baseCSSPrefix + 'dataview-item',
86869
86870     /**
86871      * @cfg {String/String[]/Ext.XTemplate} itemTpl
86872      * The inner portion of the item template to be rendered. Follows an XTemplate
86873      * structure and will be placed inside of a tpl.
86874      */
86875
86876     /**
86877      * @cfg {String} overItemCls
86878      * A CSS class to apply to each item in the view on mouseover.
86879      * Ensure {@link #trackOver} is set to `true` to make use of this.
86880      */
86881
86882     /**
86883      * @cfg {String} loadingText
86884      * A string to display during data load operations.  If specified, this text will be
86885      * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
86886      * contents will continue to display normally until the new data is loaded and the contents are replaced.
86887      */
86888     loadingText: 'Loading...',
86889
86890     /**
86891      * @cfg {Boolean/Object} loadMask
86892      * False to disable a load mask from displaying will the view is loading. This can also be a
86893      * {@link Ext.LoadMask} configuration object.
86894      */
86895     loadMask: true,
86896
86897     /**
86898      * @cfg {String} loadingCls
86899      * The CSS class to apply to the loading message element. Defaults to Ext.LoadMask.prototype.msgCls "x-mask-loading".
86900      */
86901
86902     /**
86903      * @cfg {Boolean} loadingUseMsg
86904      * Whether or not to use the loading message.
86905      * @private
86906      */
86907     loadingUseMsg: true,
86908
86909
86910     /**
86911      * @cfg {Number} loadingHeight
86912      * If specified, gives an explicit height for the data view when it is showing the {@link #loadingText},
86913      * if that is specified. This is useful to prevent the view's height from collapsing to zero when the
86914      * loading mask is applied and there are no other contents in the data view.
86915      */
86916
86917     /**
86918      * @cfg {String} [selectedItemCls='x-view-selected']
86919      * A CSS class to apply to each selected item in the view.
86920      */
86921     selectedItemCls: Ext.baseCSSPrefix + 'item-selected',
86922
86923     /**
86924      * @cfg {String} emptyText
86925      * The text to display in the view when there is no data to display.
86926      * Note that when using local data the emptyText will not be displayed unless you set
86927      * the {@link #deferEmptyText} option to false.
86928      */
86929     emptyText: "",
86930
86931     /**
86932      * @cfg {Boolean} deferEmptyText
86933      * True to defer emptyText being applied until the store's first load.
86934      */
86935     deferEmptyText: true,
86936
86937     /**
86938      * @cfg {Boolean} trackOver
86939      * True to enable mouseenter and mouseleave events
86940      */
86941     trackOver: false,
86942
86943     /**
86944      * @cfg {Boolean} blockRefresh
86945      * Set this to true to ignore datachanged events on the bound store. This is useful if
86946      * you wish to provide custom transition animations via a plugin
86947      */
86948     blockRefresh: false,
86949
86950     /**
86951      * @cfg {Boolean} disableSelection
86952      * True to disable selection within the DataView. This configuration will lock the selection model
86953      * that the DataView uses.
86954      */
86955
86956
86957     //private
86958     last: false,
86959
86960     triggerEvent: 'itemclick',
86961     triggerCtEvent: 'containerclick',
86962
86963     addCmpEvents: function() {
86964
86965     },
86966
86967     // private
86968     initComponent : function(){
86969         var me = this,
86970             isDef = Ext.isDefined,
86971             itemTpl = me.itemTpl,
86972             memberFn = {};
86973
86974         if (itemTpl) {
86975             if (Ext.isArray(itemTpl)) {
86976                 // string array
86977                 itemTpl = itemTpl.join('');
86978             } else if (Ext.isObject(itemTpl)) {
86979                 // tpl instance
86980                 memberFn = Ext.apply(memberFn, itemTpl.initialConfig);
86981                 itemTpl = itemTpl.html;
86982             }
86983
86984             if (!me.itemSelector) {
86985                 me.itemSelector = '.' + me.itemCls;
86986             }
86987
86988             itemTpl = Ext.String.format('<tpl for="."><div class="{0}">{1}</div></tpl>', me.itemCls, itemTpl);
86989             me.tpl = Ext.create('Ext.XTemplate', itemTpl, memberFn);
86990         }
86991
86992
86993         me.callParent();
86994         if(Ext.isString(me.tpl) || Ext.isArray(me.tpl)){
86995             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
86996         }
86997
86998
86999         me.addEvents(
87000             /**
87001              * @event beforerefresh
87002              * Fires before the view is refreshed
87003              * @param {Ext.view.View} this The DataView object
87004              */
87005             'beforerefresh',
87006             /**
87007              * @event refresh
87008              * Fires when the view is refreshed
87009              * @param {Ext.view.View} this The DataView object
87010              */
87011             'refresh',
87012             /**
87013              * @event viewready
87014              * Fires when the View's item elements representing Store items has been rendered. If the {@link #deferInitialRefresh} flag
87015              * was set (and it is <code>true</code> by default), this will be <b>after</b> initial render, and no items will be available
87016              * for selection until this event fires.
87017              * @param {Ext.view.View} this
87018              */
87019             'viewready',
87020             /**
87021              * @event itemupdate
87022              * Fires when the node associated with an individual record is updated
87023              * @param {Ext.data.Model} record The model instance
87024              * @param {Number} index The index of the record/node
87025              * @param {HTMLElement} node The node that has just been updated
87026              */
87027             'itemupdate',
87028             /**
87029              * @event itemadd
87030              * Fires when the nodes associated with an recordset have been added to the underlying store
87031              * @param {Ext.data.Model[]} records The model instance
87032              * @param {Number} index The index at which the set of record/nodes starts
87033              * @param {HTMLElement[]} node The node that has just been updated
87034              */
87035             'itemadd',
87036             /**
87037              * @event itemremove
87038              * Fires when the node associated with an individual record is removed
87039              * @param {Ext.data.Model} record The model instance
87040              * @param {Number} index The index of the record/node
87041              */
87042             'itemremove'
87043         );
87044
87045         me.addCmpEvents();
87046
87047         // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
87048         me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
87049         me.all = new Ext.CompositeElementLite();
87050     },
87051
87052     onRender: function() {
87053         var me = this,
87054             mask = me.loadMask,
87055             cfg = {
87056                 msg: me.loadingText,
87057                 msgCls: me.loadingCls,
87058                 useMsg: me.loadingUseMsg
87059             };
87060
87061         me.callParent(arguments);
87062
87063         if (mask) {
87064             // either a config object
87065             if (Ext.isObject(mask)) {
87066                 cfg = Ext.apply(cfg, mask);
87067             }
87068             // Attach the LoadMask to a *Component* so that it can be sensitive to resizing during long loads.
87069             // If this DataView is floating, then mask this DataView.
87070             // Otherwise, mask its owning Container (or this, if there *is* no owning Container).
87071             // LoadMask captures the element upon render.
87072             me.loadMask = Ext.create('Ext.LoadMask', me, cfg);
87073             me.loadMask.on({
87074                 scope: me,
87075                 beforeshow: me.onMaskBeforeShow,
87076                 hide: me.onMaskHide
87077             });
87078         }
87079     },
87080
87081     onMaskBeforeShow: function(){
87082         var loadingHeight = this.loadingHeight;
87083         
87084         this.getSelectionModel().deselectAll();
87085         if (loadingHeight) {
87086             this.setCalculatedSize(undefined, loadingHeight);
87087         }
87088     },
87089
87090     onMaskHide: function(){
87091         var me = this;
87092         
87093         if (!me.destroying && me.loadingHeight) {
87094             me.setHeight(me.height);
87095         }
87096     },
87097
87098     afterRender: function() {
87099         this.callParent(arguments);
87100
87101         // Init the SelectionModel after any on('render') listeners have been added.
87102         // Drag plugins create a DragDrop instance in a render listener, and that needs
87103         // to see an itemmousedown event first.
87104         this.getSelectionModel().bindComponent(this);
87105     },
87106
87107     /**
87108      * Gets the selection model for this view.
87109      * @return {Ext.selection.Model} The selection model
87110      */
87111     getSelectionModel: function(){
87112         var me = this,
87113             mode = 'SINGLE';
87114
87115         if (!me.selModel) {
87116             me.selModel = {};
87117         }
87118
87119         if (me.simpleSelect) {
87120             mode = 'SIMPLE';
87121         } else if (me.multiSelect) {
87122             mode = 'MULTI';
87123         }
87124
87125         Ext.applyIf(me.selModel, {
87126             allowDeselect: me.allowDeselect,
87127             mode: mode
87128         });
87129
87130         if (!me.selModel.events) {
87131             me.selModel = Ext.create('Ext.selection.DataViewModel', me.selModel);
87132         }
87133
87134         if (!me.selModel.hasRelaySetup) {
87135             me.relayEvents(me.selModel, [
87136                 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
87137             ]);
87138             me.selModel.hasRelaySetup = true;
87139         }
87140
87141         // lock the selection model if user
87142         // has disabled selection
87143         if (me.disableSelection) {
87144             me.selModel.locked = true;
87145         }
87146
87147         return me.selModel;
87148     },
87149
87150     /**
87151      * Refreshes the view by reloading the data from the store and re-rendering the template.
87152      */
87153     refresh: function() {
87154         var me = this,
87155             el,
87156             records;
87157
87158         if (!me.rendered || me.isDestroyed) {
87159             return;
87160         }
87161
87162         me.fireEvent('beforerefresh', me);
87163         el = me.getTargetEl();
87164         records = me.store.getRange();
87165
87166         el.update('');
87167         if (records.length < 1) {
87168             if (!me.deferEmptyText || me.hasSkippedEmptyText) {
87169                 el.update(me.emptyText);
87170             }
87171             me.all.clear();
87172         } else {
87173             me.tpl.overwrite(el, me.collectData(records, 0));
87174             me.all.fill(Ext.query(me.getItemSelector(), el.dom));
87175             me.updateIndexes(0);
87176         }
87177
87178         me.selModel.refresh();
87179         me.hasSkippedEmptyText = true;
87180         me.fireEvent('refresh', me);
87181
87182         // Upon first refresh, fire the viewready event.
87183         // Reconfiguring the grid "renews" this event.
87184         if (!me.viewReady) {
87185             // Fire an event when deferred content becomes available.
87186             // This supports grid Panel's deferRowRender capability
87187             me.viewReady = true;
87188             me.fireEvent('viewready', me);
87189         }
87190     },
87191
87192     /**
87193      * Function which can be overridden to provide custom formatting for each Record that is used by this
87194      * DataView's {@link #tpl template} to render each node.
87195      * @param {Object/Object[]} data The raw data object that was used to create the Record.
87196      * @param {Number} recordIndex the index number of the Record being prepared for rendering.
87197      * @param {Ext.data.Model} record The Record being prepared for rendering.
87198      * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
87199      * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
87200      */
87201     prepareData: function(data, index, record) {
87202         if (record) {
87203             Ext.apply(data, record.getAssociatedData());
87204         }
87205         return data;
87206     },
87207
87208     /**
87209      * <p>Function which can be overridden which returns the data object passed to this
87210      * DataView's {@link #tpl template} to render the whole DataView.</p>
87211      * <p>This is usually an Array of data objects, each element of which is processed by an
87212      * {@link Ext.XTemplate XTemplate} which uses <tt>'&lt;tpl for="."&gt;'</tt> to iterate over its supplied
87213      * data object as an Array. However, <i>named</i> properties may be placed into the data object to
87214      * provide non-repeating data such as headings, totals etc.</p>
87215      * @param {Ext.data.Model[]} records An Array of {@link Ext.data.Model}s to be rendered into the DataView.
87216      * @param {Number} startIndex the index number of the Record being prepared for rendering.
87217      * @return {Object[]} An Array of data objects to be processed by a repeating XTemplate. May also
87218      * contain <i>named</i> properties.
87219      */
87220     collectData : function(records, startIndex){
87221         var r = [],
87222             i = 0,
87223             len = records.length,
87224             record;
87225
87226         for(; i < len; i++){
87227             record = records[i];
87228             r[r.length] = this.prepareData(record[record.persistenceProperty], startIndex + i, record);
87229         }
87230         return r;
87231     },
87232
87233     // private
87234     bufferRender : function(records, index){
87235         var div = document.createElement('div');
87236         this.tpl.overwrite(div, this.collectData(records, index));
87237         return Ext.query(this.getItemSelector(), div);
87238     },
87239
87240     // private
87241     onUpdate : function(ds, record){
87242         var me = this,
87243             index = me.store.indexOf(record),
87244             node;
87245
87246         if (index > -1){
87247             node = me.bufferRender([record], index)[0];
87248             // ensure the node actually exists in the DOM
87249             if (me.getNode(record)) {
87250                 me.all.replaceElement(index, node, true);
87251                 me.updateIndexes(index, index);
87252                 // Maintain selection after update
87253                 // TODO: Move to approriate event handler.
87254                 me.selModel.refresh();
87255                 me.fireEvent('itemupdate', record, index, node);
87256             }
87257         }
87258
87259     },
87260
87261     // private
87262     onAdd : function(ds, records, index) {
87263         var me = this,
87264             nodes;
87265
87266         if (me.all.getCount() === 0) {
87267             me.refresh();
87268             return;
87269         }
87270
87271         nodes = me.bufferRender(records, index);
87272         me.doAdd(nodes, records, index);
87273
87274         me.selModel.refresh();
87275         me.updateIndexes(index);
87276         me.fireEvent('itemadd', records, index, nodes);
87277     },
87278
87279     doAdd: function(nodes, records, index) {
87280         var all = this.all;
87281
87282         if (index < all.getCount()) {
87283             all.item(index).insertSibling(nodes, 'before', true);
87284         } else {
87285             all.last().insertSibling(nodes, 'after', true);
87286         }
87287
87288         Ext.Array.insert(all.elements, index, nodes);
87289     },
87290
87291     // private
87292     onRemove : function(ds, record, index) {
87293         var me = this;
87294
87295         me.doRemove(record, index);
87296         me.updateIndexes(index);
87297         if (me.store.getCount() === 0){
87298             me.refresh();
87299         }
87300         me.fireEvent('itemremove', record, index);
87301     },
87302
87303     doRemove: function(record, index) {
87304         this.all.removeElement(index, true);
87305     },
87306
87307     /**
87308      * Refreshes an individual node's data from the store.
87309      * @param {Number} index The item's data index in the store
87310      */
87311     refreshNode : function(index){
87312         this.onUpdate(this.store, this.store.getAt(index));
87313     },
87314
87315     // private
87316     updateIndexes : function(startIndex, endIndex) {
87317         var ns = this.all.elements,
87318             records = this.store.getRange(),
87319             i;
87320             
87321         startIndex = startIndex || 0;
87322         endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
87323         for(i = startIndex; i <= endIndex; i++){
87324             ns[i].viewIndex = i;
87325             ns[i].viewRecordId = records[i].internalId;
87326             if (!ns[i].boundView) {
87327                 ns[i].boundView = this.id;
87328             }
87329         }
87330     },
87331
87332     /**
87333      * Returns the store associated with this DataView.
87334      * @return {Ext.data.Store} The store
87335      */
87336     getStore : function(){
87337         return this.store;
87338     },
87339
87340     /**
87341      * Changes the data store bound to this view and refreshes it.
87342      * @param {Ext.data.Store} store The store to bind to this view
87343      */
87344     bindStore : function(store, initial) {
87345         var me = this,
87346             maskStore;
87347
87348         if (!initial && me.store) {
87349             if (store !== me.store && me.store.autoDestroy) {
87350                 me.store.destroyStore();
87351             }
87352             else {
87353                 me.mun(me.store, {
87354                     scope: me,
87355                     datachanged: me.onDataChanged,
87356                     add: me.onAdd,
87357                     remove: me.onRemove,
87358                     update: me.onUpdate,
87359                     clear: me.refresh
87360                 });
87361             }
87362             if (!store) {
87363                 // Ensure we have an instantiated LoadMask before we unbind it.
87364                 if (me.loadMask && me.loadMask.bindStore) {
87365                     me.loadMask.bindStore(null);
87366                 }
87367                 me.store = null;
87368             }
87369         }
87370         if (store) {
87371             store = Ext.data.StoreManager.lookup(store);
87372             me.mon(store, {
87373                 scope: me,
87374                 datachanged: me.onDataChanged,
87375                 add: me.onAdd,
87376                 remove: me.onRemove,
87377                 update: me.onUpdate,
87378                 clear: me.refresh
87379             });
87380             // Ensure we have an instantiated LoadMask before we bind it.
87381             if (me.loadMask && me.loadMask.bindStore) {
87382                 // View's store is a NodeStore, use owning TreePanel's Store
87383                 if (Ext.Array.contains(store.alias, 'store.node')) {
87384                     maskStore = this.ownerCt.store;
87385                 } else {
87386                     maskStore = store;
87387                 }
87388                 me.loadMask.bindStore(maskStore);
87389             }
87390         }
87391
87392         // Flag to say that initial refresh has not been performed.
87393         // Set here rather than at initialization time, so that a reconfigure with a new store will refire viewready
87394         me.viewReady = false;
87395
87396         me.store = store;
87397         // Bind the store to our selection model
87398         me.getSelectionModel().bind(store);
87399
87400         /*
87401          * This code used to have checks for:
87402          * if (store && (!initial || store.getCount() || me.emptyText)) {
87403          * Instead, just trigger a refresh and let the view itself figure out
87404          * what needs to happen. It can cause incorrect display if our store
87405          * has no data.
87406          */
87407         if (store) {
87408             if (initial && me.deferInitialRefresh) {
87409                 Ext.Function.defer(function () {
87410                     if (!me.isDestroyed) {
87411                         me.refresh(true);
87412                     }
87413                 }, 1);
87414             } else {
87415                 me.refresh(true);
87416             }
87417         }
87418     },
87419
87420     /**
87421      * @private
87422      * Calls this.refresh if this.blockRefresh is not true
87423      */
87424     onDataChanged: function() {
87425         if (this.blockRefresh !== true) {
87426             this.refresh.apply(this, arguments);
87427         }
87428     },
87429
87430     /**
87431      * Returns the template node the passed child belongs to, or null if it doesn't belong to one.
87432      * @param {HTMLElement} node
87433      * @return {HTMLElement} The template node
87434      */
87435     findItemByChild: function(node){
87436         return Ext.fly(node).findParent(this.getItemSelector(), this.getTargetEl());
87437     },
87438
87439     /**
87440      * Returns the template node by the Ext.EventObject or null if it is not found.
87441      * @param {Ext.EventObject} e
87442      */
87443     findTargetByEvent: function(e) {
87444         return e.getTarget(this.getItemSelector(), this.getTargetEl());
87445     },
87446
87447
87448     /**
87449      * Gets the currently selected nodes.
87450      * @return {HTMLElement[]} An array of HTMLElements
87451      */
87452     getSelectedNodes: function(){
87453         var nodes   = [],
87454             records = this.selModel.getSelection(),
87455             ln = records.length,
87456             i  = 0;
87457
87458         for (; i < ln; i++) {
87459             nodes.push(this.getNode(records[i]));
87460         }
87461
87462         return nodes;
87463     },
87464
87465     /**
87466      * Gets an array of the records from an array of nodes
87467      * @param {HTMLElement[]} nodes The nodes to evaluate
87468      * @return {Ext.data.Model[]} records The {@link Ext.data.Model} objects
87469      */
87470     getRecords: function(nodes) {
87471         var records = [],
87472             i = 0,
87473             len = nodes.length,
87474             data = this.store.data;
87475
87476         for (; i < len; i++) {
87477             records[records.length] = data.getByKey(nodes[i].viewRecordId);
87478         }
87479
87480         return records;
87481     },
87482
87483     /**
87484      * Gets a record from a node
87485      * @param {Ext.Element/HTMLElement} node The node to evaluate
87486      *
87487      * @return {Ext.data.Model} record The {@link Ext.data.Model} object
87488      */
87489     getRecord: function(node){
87490         return this.store.data.getByKey(Ext.getDom(node).viewRecordId);
87491     },
87492
87493
87494     /**
87495      * Returns true if the passed node is selected, else false.
87496      * @param {HTMLElement/Number/Ext.data.Model} node The node, node index or record to check
87497      * @return {Boolean} True if selected, else false
87498      */
87499     isSelected : function(node) {
87500         // TODO: El/Idx/Record
87501         var r = this.getRecord(node);
87502         return this.selModel.isSelected(r);
87503     },
87504
87505     /**
87506      * Selects a record instance by record instance or index.
87507      * @param {Ext.data.Model[]/Number} records An array of records or an index
87508      * @param {Boolean} [keepExisting] True to keep existing selections
87509      * @param {Boolean} [suppressEvent] Set to true to not fire a select event
87510      */
87511     select: function(records, keepExisting, suppressEvent) {
87512         this.selModel.select(records, keepExisting, suppressEvent);
87513     },
87514
87515     /**
87516      * Deselects a record instance by record instance or index.
87517      * @param {Ext.data.Model[]/Number} records An array of records or an index
87518      * @param {Boolean} [suppressEvent] Set to true to not fire a deselect event
87519      */
87520     deselect: function(records, suppressEvent) {
87521         this.selModel.deselect(records, suppressEvent);
87522     },
87523
87524     /**
87525      * Gets a template node.
87526      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node,
87527      * the id of a template node or the record associated with the node.
87528      * @return {HTMLElement} The node or null if it wasn't found
87529      */
87530     getNode : function(nodeInfo) {
87531         if (!this.rendered) {
87532             return null;
87533         }
87534         if (Ext.isString(nodeInfo)) {
87535             return document.getElementById(nodeInfo);
87536         }
87537         if (Ext.isNumber(nodeInfo)) {
87538             return this.all.elements[nodeInfo];
87539         }
87540         if (nodeInfo instanceof Ext.data.Model) {
87541             return this.getNodeByRecord(nodeInfo);
87542         }
87543         return nodeInfo; // already an HTMLElement
87544     },
87545
87546     /**
87547      * @private
87548      */
87549     getNodeByRecord: function(record) {
87550         var ns = this.all.elements,
87551             ln = ns.length,
87552             i = 0;
87553
87554         for (; i < ln; i++) {
87555             if (ns[i].viewRecordId === record.internalId) {
87556                 return ns[i];
87557             }
87558         }
87559
87560         return null;
87561     },
87562
87563     /**
87564      * Gets a range nodes.
87565      * @param {Number} start (optional) The index of the first node in the range
87566      * @param {Number} end (optional) The index of the last node in the range
87567      * @return {HTMLElement[]} An array of nodes
87568      */
87569     getNodes: function(start, end) {
87570         var ns = this.all.elements,
87571             nodes = [],
87572             i;
87573
87574         start = start || 0;
87575         end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
87576         if (start <= end) {
87577             for (i = start; i <= end && ns[i]; i++) {
87578                 nodes.push(ns[i]);
87579             }
87580         } else {
87581             for (i = start; i >= end && ns[i]; i--) {
87582                 nodes.push(ns[i]);
87583             }
87584         }
87585         return nodes;
87586     },
87587
87588     /**
87589      * Finds the index of the passed node.
87590      * @param {HTMLElement/String/Number/Ext.data.Model} nodeInfo An HTMLElement template node, index of a template node, the id of a template node
87591      * or a record associated with a node.
87592      * @return {Number} The index of the node or -1
87593      */
87594     indexOf: function(node) {
87595         node = this.getNode(node);
87596         if (Ext.isNumber(node.viewIndex)) {
87597             return node.viewIndex;
87598         }
87599         return this.all.indexOf(node);
87600     },
87601
87602     onDestroy : function() {
87603         var me = this;
87604
87605         me.all.clear();
87606         me.callParent();
87607         me.bindStore(null);
87608         me.selModel.destroy();
87609     },
87610
87611     // invoked by the selection model to maintain visual UI cues
87612     onItemSelect: function(record) {
87613         var node = this.getNode(record);
87614         
87615         if (node) {
87616             Ext.fly(node).addCls(this.selectedItemCls);
87617         }
87618     },
87619
87620     // invoked by the selection model to maintain visual UI cues
87621     onItemDeselect: function(record) {
87622         var node = this.getNode(record);
87623         
87624         if (node) {
87625             Ext.fly(node).removeCls(this.selectedItemCls);
87626         }
87627     },
87628
87629     getItemSelector: function() {
87630         return this.itemSelector;
87631     }
87632 }, function() {
87633     // all of this information is available directly
87634     // from the SelectionModel itself, the only added methods
87635     // to DataView regarding selection will perform some transformation/lookup
87636     // between HTMLElement/Nodes to records and vice versa.
87637     Ext.deprecate('extjs', '4.0', function() {
87638         Ext.view.AbstractView.override({
87639             /**
87640              * @cfg {Boolean} [multiSelect=false]
87641              * True to allow selection of more than one item at a time, false to allow selection of only a single item
87642              * at a time or no selection at all, depending on the value of {@link #singleSelect}.
87643              */
87644             /**
87645              * @cfg {Boolean} [singleSelect=false]
87646              * True to allow selection of exactly one item at a time, false to allow no selection at all.
87647              * Note that if {@link #multiSelect} = true, this value will be ignored.
87648              */
87649             /**
87650              * @cfg {Boolean} [simpleSelect=false]
87651              * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
87652              * false to force the user to hold Ctrl or Shift to select more than on item.
87653              */
87654
87655             /**
87656              * Gets the number of selected nodes.
87657              * @return {Number} The node count
87658              */
87659             getSelectionCount : function(){
87660                 if (Ext.global.console) {
87661                     Ext.global.console.warn("DataView: getSelectionCount will be removed, please interact with the Ext.selection.DataViewModel");
87662                 }
87663                 return this.selModel.getSelection().length;
87664             },
87665
87666             /**
87667              * Gets an array of the selected records
87668              * @return {Ext.data.Model[]} An array of {@link Ext.data.Model} objects
87669              */
87670             getSelectedRecords : function(){
87671                 if (Ext.global.console) {
87672                     Ext.global.console.warn("DataView: getSelectedRecords will be removed, please interact with the Ext.selection.DataViewModel");
87673                 }
87674                 return this.selModel.getSelection();
87675             },
87676
87677             select: function(records, keepExisting, supressEvents) {
87678                 if (Ext.global.console) {
87679                     Ext.global.console.warn("DataView: select will be removed, please access select through a DataView's SelectionModel, ie: view.getSelectionModel().select()");
87680                 }
87681                 var sm = this.getSelectionModel();
87682                 return sm.select.apply(sm, arguments);
87683             },
87684
87685             clearSelections: function() {
87686                 if (Ext.global.console) {
87687                     Ext.global.console.warn("DataView: clearSelections will be removed, please access deselectAll through DataView's SelectionModel, ie: view.getSelectionModel().deselectAll()");
87688                 }
87689                 var sm = this.getSelectionModel();
87690                 return sm.deselectAll();
87691             }
87692         });
87693     });
87694 });
87695
87696 /**
87697  * @class Ext.Action
87698  * <p>An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it
87699  * can be usefully shared among multiple components.  Actions let you share handlers, configuration options and UI
87700  * updates across any components that support the Action interface (primarily {@link Ext.toolbar.Toolbar}, {@link Ext.button.Button}
87701  * and {@link Ext.menu.Menu} components).</p>
87702  * <p>Use a single Action instance as the config object for any number of UI Components which share the same configuration. The
87703  * Action not only supplies the configuration, but allows all Components based upon it to have a common set of methods
87704  * called at once through a single call to the Action.</p>
87705  * <p>Any Component that is to be configured with an Action must also support
87706  * the following methods:<ul>
87707  * <li><code>setText(string)</code></li>
87708  * <li><code>setIconCls(string)</code></li>
87709  * <li><code>setDisabled(boolean)</code></li>
87710  * <li><code>setVisible(boolean)</code></li>
87711  * <li><code>setHandler(function)</code></li></ul></p>
87712  * <p>This allows the Action to control its associated Components.</p>
87713  * Example usage:<br>
87714  * <pre><code>
87715 // Define the shared Action.  Each Component below will have the same
87716 // display text and icon, and will display the same message on click.
87717 var action = new Ext.Action({
87718     {@link #text}: 'Do something',
87719     {@link #handler}: function(){
87720         Ext.Msg.alert('Click', 'You did something.');
87721     },
87722     {@link #iconCls}: 'do-something',
87723     {@link #itemId}: 'myAction'
87724 });
87725
87726 var panel = new Ext.panel.Panel({
87727     title: 'Actions',
87728     width: 500,
87729     height: 300,
87730     tbar: [
87731         // Add the Action directly to a toolbar as a menu button
87732         action,
87733         {
87734             text: 'Action Menu',
87735             // Add the Action to a menu as a text item
87736             menu: [action]
87737         }
87738     ],
87739     items: [
87740         // Add the Action to the panel body as a standard button
87741         new Ext.button.Button(action)
87742     ],
87743     renderTo: Ext.getBody()
87744 });
87745
87746 // Change the text for all components using the Action
87747 action.setText('Something else');
87748
87749 // Reference an Action through a container using the itemId
87750 var btn = panel.getComponent('myAction');
87751 var aRef = btn.baseAction;
87752 aRef.setText('New text');
87753 </code></pre>
87754  */
87755 Ext.define('Ext.Action', {
87756
87757     /* Begin Definitions */
87758
87759     /* End Definitions */
87760
87761     /**
87762      * @cfg {String} [text='']
87763      * The text to set for all components configured by this Action.
87764      */
87765     /**
87766      * @cfg {String} [iconCls='']
87767      * The CSS class selector that specifies a background image to be used as the header icon for
87768      * all components configured by this Action.
87769      * <p>An example of specifying a custom icon class would be something like:
87770      * </p><pre><code>
87771 // specify the property in the config for the class:
87772      ...
87773      iconCls: 'do-something'
87774
87775 // css class that specifies background image to be used as the icon image:
87776 .do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
87777 </code></pre>
87778      */
87779     /**
87780      * @cfg {Boolean} [disabled=false]
87781      * True to disable all components configured by this Action, false to enable them.
87782      */
87783     /**
87784      * @cfg {Boolean} [hidden=false]
87785      * True to hide all components configured by this Action, false to show them.
87786      */
87787     /**
87788      * @cfg {Function} handler
87789      * The function that will be invoked by each component tied to this Action
87790      * when the component's primary event is triggered.
87791      */
87792     /**
87793      * @cfg {String} itemId
87794      * See {@link Ext.Component}.{@link Ext.Component#itemId itemId}.
87795      */
87796     /**
87797      * @cfg {Object} scope
87798      * The scope (this reference) in which the {@link #handler} is executed.
87799      * Defaults to the browser window.
87800      */
87801
87802     /**
87803      * Creates new Action.
87804      * @param {Object} config Config object.
87805      */
87806     constructor : function(config){
87807         this.initialConfig = config;
87808         this.itemId = config.itemId = (config.itemId || config.id || Ext.id());
87809         this.items = [];
87810     },
87811
87812     // private
87813     isAction : true,
87814
87815     /**
87816      * Sets the text to be displayed by all components configured by this Action.
87817      * @param {String} text The text to display
87818      */
87819     setText : function(text){
87820         this.initialConfig.text = text;
87821         this.callEach('setText', [text]);
87822     },
87823
87824     /**
87825      * Gets the text currently displayed by all components configured by this Action.
87826      */
87827     getText : function(){
87828         return this.initialConfig.text;
87829     },
87830
87831     /**
87832      * Sets the icon CSS class for all components configured by this Action.  The class should supply
87833      * a background image that will be used as the icon image.
87834      * @param {String} cls The CSS class supplying the icon image
87835      */
87836     setIconCls : function(cls){
87837         this.initialConfig.iconCls = cls;
87838         this.callEach('setIconCls', [cls]);
87839     },
87840
87841     /**
87842      * Gets the icon CSS class currently used by all components configured by this Action.
87843      */
87844     getIconCls : function(){
87845         return this.initialConfig.iconCls;
87846     },
87847
87848     /**
87849      * Sets the disabled state of all components configured by this Action.  Shortcut method
87850      * for {@link #enable} and {@link #disable}.
87851      * @param {Boolean} disabled True to disable the component, false to enable it
87852      */
87853     setDisabled : function(v){
87854         this.initialConfig.disabled = v;
87855         this.callEach('setDisabled', [v]);
87856     },
87857
87858     /**
87859      * Enables all components configured by this Action.
87860      */
87861     enable : function(){
87862         this.setDisabled(false);
87863     },
87864
87865     /**
87866      * Disables all components configured by this Action.
87867      */
87868     disable : function(){
87869         this.setDisabled(true);
87870     },
87871
87872     /**
87873      * Returns true if the components using this Action are currently disabled, else returns false.
87874      */
87875     isDisabled : function(){
87876         return this.initialConfig.disabled;
87877     },
87878
87879     /**
87880      * Sets the hidden state of all components configured by this Action.  Shortcut method
87881      * for <code>{@link #hide}</code> and <code>{@link #show}</code>.
87882      * @param {Boolean} hidden True to hide the component, false to show it
87883      */
87884     setHidden : function(v){
87885         this.initialConfig.hidden = v;
87886         this.callEach('setVisible', [!v]);
87887     },
87888
87889     /**
87890      * Shows all components configured by this Action.
87891      */
87892     show : function(){
87893         this.setHidden(false);
87894     },
87895
87896     /**
87897      * Hides all components configured by this Action.
87898      */
87899     hide : function(){
87900         this.setHidden(true);
87901     },
87902
87903     /**
87904      * Returns true if the components configured by this Action are currently hidden, else returns false.
87905      */
87906     isHidden : function(){
87907         return this.initialConfig.hidden;
87908     },
87909
87910     /**
87911      * Sets the function that will be called by each Component using this action when its primary event is triggered.
87912      * @param {Function} fn The function that will be invoked by the action's components.  The function
87913      * will be called with no arguments.
87914      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component firing the event.
87915      */
87916     setHandler : function(fn, scope){
87917         this.initialConfig.handler = fn;
87918         this.initialConfig.scope = scope;
87919         this.callEach('setHandler', [fn, scope]);
87920     },
87921
87922     /**
87923      * Executes the specified function once for each Component currently tied to this Action.  The function passed
87924      * in should accept a single argument that will be an object that supports the basic Action config/method interface.
87925      * @param {Function} fn The function to execute for each component
87926      * @param {Object} scope The scope (<code>this</code> reference) in which the function is executed.  Defaults to the Component.
87927      */
87928     each : function(fn, scope){
87929         Ext.each(this.items, fn, scope);
87930     },
87931
87932     // private
87933     callEach : function(fnName, args){
87934         var items = this.items,
87935             i = 0,
87936             len = items.length;
87937
87938         for(; i < len; i++){
87939             items[i][fnName].apply(items[i], args);
87940         }
87941     },
87942
87943     // private
87944     addComponent : function(comp){
87945         this.items.push(comp);
87946         comp.on('destroy', this.removeComponent, this);
87947     },
87948
87949     // private
87950     removeComponent : function(comp){
87951         Ext.Array.remove(this.items, comp);
87952     },
87953
87954     /**
87955      * Executes this Action manually using the handler function specified in the original config object
87956      * or the handler function set with <code>{@link #setHandler}</code>.  Any arguments passed to this
87957      * function will be passed on to the handler function.
87958      * @param {Object...} args (optional) Variable number of arguments passed to the handler function
87959      */
87960     execute : function(){
87961         this.initialConfig.handler.apply(this.initialConfig.scope || Ext.global, arguments);
87962     }
87963 });
87964
87965 /**
87966  * Component layout for editors
87967  * @class Ext.layout.component.Editor
87968  * @extends Ext.layout.component.Component
87969  * @private
87970  */
87971 Ext.define('Ext.layout.component.Editor', {
87972
87973     /* Begin Definitions */
87974
87975     alias: ['layout.editor'],
87976
87977     extend: 'Ext.layout.component.Component',
87978
87979     /* End Definitions */
87980
87981     onLayout: function(width, height) {
87982         var me = this,
87983             owner = me.owner,
87984             autoSize = owner.autoSize;
87985             
87986         if (autoSize === true) {
87987             autoSize = {
87988                 width: 'field',
87989                 height: 'field'    
87990             };
87991         }
87992         
87993         if (autoSize) {
87994             width = me.getDimension(owner, autoSize.width, 'Width', width);
87995             height = me.getDimension(owner, autoSize.height, 'Height', height);
87996         }
87997         me.setTargetSize(width, height);
87998         owner.field.setSize(width, height);
87999     },
88000     
88001     getDimension: function(owner, type, dimension, actual){
88002         var method = 'get' + dimension;
88003         switch (type) {
88004             case 'boundEl':
88005                 return owner.boundEl[method]();
88006             case 'field':
88007                 return owner.field[method]();
88008             default:
88009                 return actual;
88010         }
88011     }
88012 });
88013 /**
88014  * @class Ext.Editor
88015  * @extends Ext.Component
88016  *
88017  * <p>
88018  * The Editor class is used to provide inline editing for elements on the page. The editor
88019  * is backed by a {@link Ext.form.field.Field} that will be displayed to edit the underlying content.
88020  * The editor is a floating Component, when the editor is shown it is automatically aligned to
88021  * display over the top of the bound element it is editing. The Editor contains several options
88022  * for how to handle key presses:
88023  * <ul>
88024  * <li>{@link #completeOnEnter}</li>
88025  * <li>{@link #cancelOnEsc}</li>
88026  * <li>{@link #swallowKeys}</li>
88027  * </ul>
88028  * It also has options for how to use the value once the editor has been activated:
88029  * <ul>
88030  * <li>{@link #revertInvalid}</li>
88031  * <li>{@link #ignoreNoChange}</li>
88032  * <li>{@link #updateEl}</li>
88033  * </ul>
88034  * Sample usage:
88035  * </p>
88036  * <pre><code>
88037 var editor = new Ext.Editor({
88038     updateEl: true, // update the innerHTML of the bound element when editing completes
88039     field: {
88040         xtype: 'textfield'
88041     }
88042 });
88043 var el = Ext.get('my-text'); // The element to 'edit'
88044 editor.startEdit(el); // The value of the field will be taken as the innerHTML of the element.
88045  * </code></pre>
88046  * {@img Ext.Editor/Ext.Editor.png Ext.Editor component}
88047  *
88048  */
88049 Ext.define('Ext.Editor', {
88050
88051     /* Begin Definitions */
88052
88053     extend: 'Ext.Component',
88054
88055     alias: 'widget.editor',
88056
88057     requires: ['Ext.layout.component.Editor'],
88058
88059     /* End Definitions */
88060
88061    componentLayout: 'editor',
88062
88063     /**
88064     * @cfg {Ext.form.field.Field} field
88065     * The Field object (or descendant) or config object for field
88066     */
88067
88068     /**
88069      * @cfg {Boolean} allowBlur
88070      * True to {@link #completeEdit complete the editing process} if in edit mode when the
88071      * field is blurred.
88072      */
88073     allowBlur: true,
88074
88075     /**
88076      * @cfg {Boolean/Object} autoSize
88077      * True for the editor to automatically adopt the size of the underlying field. Otherwise, an object
88078      * can be passed to indicate where to get each dimension. The available properties are 'boundEl' and
88079      * 'field'. If a dimension is not specified, it will use the underlying height/width specified on
88080      * the editor object.
88081      * Examples:
88082      * <pre><code>
88083 autoSize: true // The editor will be sized to the height/width of the field
88084
88085 height: 21,
88086 autoSize: {
88087     width: 'boundEl' // The width will be determined by the width of the boundEl, the height from the editor (21)
88088 }
88089
88090 autoSize: {
88091     width: 'field', // Width from the field
88092     height: 'boundEl' // Height from the boundEl
88093 }
88094      * </pre></code>
88095      */
88096
88097     /**
88098      * @cfg {Boolean} revertInvalid
88099      * True to automatically revert the field value and cancel the edit when the user completes an edit and the field
88100      * validation fails
88101      */
88102     revertInvalid: true,
88103
88104     /**
88105      * @cfg {Boolean} [ignoreNoChange=false]
88106      * True to skip the edit completion process (no save, no events fired) if the user completes an edit and
88107      * the value has not changed.  Applies only to string values - edits for other data types
88108      * will never be ignored.
88109      */
88110
88111     /**
88112      * @cfg {Boolean} [hideEl=true]
88113      * False to keep the bound element visible while the editor is displayed
88114      */
88115
88116     /**
88117      * @cfg {Object} value
88118      * The data value of the underlying field
88119      */
88120     value : '',
88121
88122     /**
88123      * @cfg {String} alignment
88124      * The position to align to (see {@link Ext.Element#alignTo} for more details).
88125      */
88126     alignment: 'c-c?',
88127
88128     /**
88129      * @cfg {Number[]} offsets
88130      * The offsets to use when aligning (see {@link Ext.Element#alignTo} for more details.
88131      */
88132     offsets: [0, 0],
88133
88134     /**
88135      * @cfg {Boolean/String} shadow
88136      * "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop" for bottom-right shadow.
88137      */
88138     shadow : 'frame',
88139
88140     /**
88141      * @cfg {Boolean} constrain
88142      * True to constrain the editor to the viewport
88143      */
88144     constrain : false,
88145
88146     /**
88147      * @cfg {Boolean} swallowKeys
88148      * Handle the keydown/keypress events so they don't propagate
88149      */
88150     swallowKeys : true,
88151
88152     /**
88153      * @cfg {Boolean} completeOnEnter
88154      * True to complete the edit when the enter key is pressed.
88155      */
88156     completeOnEnter : true,
88157
88158     /**
88159      * @cfg {Boolean} cancelOnEsc
88160      * True to cancel the edit when the escape key is pressed.
88161      */
88162     cancelOnEsc : true,
88163
88164     /**
88165      * @cfg {Boolean} updateEl
88166      * True to update the innerHTML of the bound element when the update completes
88167      */
88168     updateEl : false,
88169
88170     /**
88171      * @cfg {String/HTMLElement/Ext.Element} parentEl
88172      * An element to render to. Defaults to the <tt>document.body</tt>.
88173      */
88174
88175     // private overrides
88176     hidden: true,
88177     baseCls: Ext.baseCSSPrefix + 'editor',
88178
88179     initComponent : function() {
88180         var me = this,
88181             field = me.field = Ext.ComponentManager.create(me.field, 'textfield');
88182
88183         Ext.apply(field, {
88184             inEditor: true,
88185             msgTarget: field.msgTarget == 'title' ? 'title' :  'qtip'
88186         });
88187         me.mon(field, {
88188             scope: me,
88189             blur: {
88190                 fn: me.onBlur,
88191                 // slight delay to avoid race condition with startEdits (e.g. grid view refresh)
88192                 delay: 1
88193             },
88194             specialkey: me.onSpecialKey
88195         });
88196
88197         if (field.grow) {
88198             me.mon(field, 'autosize', me.onAutoSize,  me, {delay: 1});
88199         }
88200         me.floating = {
88201             constrain: me.constrain
88202         };
88203
88204         me.callParent(arguments);
88205
88206         me.addEvents(
88207             /**
88208              * @event beforestartedit
88209              * Fires when editing is initiated, but before the value changes.  Editing can be canceled by returning
88210              * false from the handler of this event.
88211              * @param {Ext.Editor} this
88212              * @param {Ext.Element} boundEl The underlying element bound to this editor
88213              * @param {Object} value The field value being set
88214              */
88215             'beforestartedit',
88216
88217             /**
88218              * @event startedit
88219              * Fires when this editor is displayed
88220              * @param {Ext.Editor} this
88221              * @param {Ext.Element} boundEl The underlying element bound to this editor
88222              * @param {Object} value The starting field value
88223              */
88224             'startedit',
88225
88226             /**
88227              * @event beforecomplete
88228              * Fires after a change has been made to the field, but before the change is reflected in the underlying
88229              * field.  Saving the change to the field can be canceled by returning false from the handler of this event.
88230              * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
88231              * event will not fire since no edit actually occurred.
88232              * @param {Ext.Editor} this
88233              * @param {Object} value The current field value
88234              * @param {Object} startValue The original field value
88235              */
88236             'beforecomplete',
88237             /**
88238              * @event complete
88239              * Fires after editing is complete and any changed value has been written to the underlying field.
88240              * @param {Ext.Editor} this
88241              * @param {Object} value The current field value
88242              * @param {Object} startValue The original field value
88243              */
88244             'complete',
88245             /**
88246              * @event canceledit
88247              * Fires after editing has been canceled and the editor's value has been reset.
88248              * @param {Ext.Editor} this
88249              * @param {Object} value The user-entered field value that was discarded
88250              * @param {Object} startValue The original field value that was set back into the editor after cancel
88251              */
88252             'canceledit',
88253             /**
88254              * @event specialkey
88255              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.  You can check
88256              * {@link Ext.EventObject#getKey} to determine which key was pressed.
88257              * @param {Ext.Editor} this
88258              * @param {Ext.form.field.Field} The field attached to this editor
88259              * @param {Ext.EventObject} event The event object
88260              */
88261             'specialkey'
88262         );
88263     },
88264
88265     // private
88266     onAutoSize: function(){
88267         this.doComponentLayout();
88268     },
88269
88270     // private
88271     onRender : function(ct, position) {
88272         var me = this,
88273             field = me.field,
88274             inputEl = field.inputEl;
88275
88276         me.callParent(arguments);
88277
88278         field.render(me.el);
88279         //field.hide();
88280         // Ensure the field doesn't get submitted as part of any form
88281         if (inputEl) {
88282             inputEl.dom.name = '';
88283             if (me.swallowKeys) {
88284                 inputEl.swallowEvent([
88285                     'keypress', // *** Opera
88286                     'keydown'   // *** all other browsers
88287                 ]);
88288             }
88289         }
88290     },
88291
88292     // private
88293     onSpecialKey : function(field, event) {
88294         var me = this,
88295             key = event.getKey(),
88296             complete = me.completeOnEnter && key == event.ENTER,
88297             cancel = me.cancelOnEsc && key == event.ESC;
88298
88299         if (complete || cancel) {
88300             event.stopEvent();
88301             // Must defer this slightly to prevent exiting edit mode before the field's own
88302             // key nav can handle the enter key, e.g. selecting an item in a combobox list
88303             Ext.defer(function() {
88304                 if (complete) {
88305                     me.completeEdit();
88306                 } else {
88307                     me.cancelEdit();
88308                 }
88309                 if (field.triggerBlur) {
88310                     field.triggerBlur();
88311                 }
88312             }, 10);
88313         }
88314
88315         this.fireEvent('specialkey', this, field, event);
88316     },
88317
88318     /**
88319      * Starts the editing process and shows the editor.
88320      * @param {String/HTMLElement/Ext.Element} el The element to edit
88321      * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults
88322       * to the innerHTML of el.
88323      */
88324     startEdit : function(el, value) {
88325         var me = this,
88326             field = me.field;
88327
88328         me.completeEdit();
88329         me.boundEl = Ext.get(el);
88330         value = Ext.isDefined(value) ? value : me.boundEl.dom.innerHTML;
88331
88332         if (!me.rendered) {
88333             me.render(me.parentEl || document.body);
88334         }
88335
88336         if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) {
88337             me.startValue = value;
88338             me.show();
88339             field.reset();
88340             field.setValue(value);
88341             me.realign(true);
88342             field.focus(false, 10);
88343             if (field.autoSize) {
88344                 field.autoSize();
88345             }
88346             me.editing = true;
88347         }
88348     },
88349
88350     /**
88351      * Realigns the editor to the bound field based on the current alignment config value.
88352      * @param {Boolean} autoSize (optional) True to size the field to the dimensions of the bound element.
88353      */
88354     realign : function(autoSize) {
88355         var me = this;
88356         if (autoSize === true) {
88357             me.doComponentLayout();
88358         }
88359         me.alignTo(me.boundEl, me.alignment, me.offsets);
88360     },
88361
88362     /**
88363      * Ends the editing process, persists the changed value to the underlying field, and hides the editor.
88364      * @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after edit
88365      */
88366     completeEdit : function(remainVisible) {
88367         var me = this,
88368             field = me.field,
88369             value;
88370
88371         if (!me.editing) {
88372             return;
88373         }
88374
88375         // Assert combo values first
88376         if (field.assertValue) {
88377             field.assertValue();
88378         }
88379
88380         value = me.getValue();
88381         if (!field.isValid()) {
88382             if (me.revertInvalid !== false) {
88383                 me.cancelEdit(remainVisible);
88384             }
88385             return;
88386         }
88387
88388         if (String(value) === String(me.startValue) && me.ignoreNoChange) {
88389             me.hideEdit(remainVisible);
88390             return;
88391         }
88392
88393         if (me.fireEvent('beforecomplete', me, value, me.startValue) !== false) {
88394             // Grab the value again, may have changed in beforecomplete
88395             value = me.getValue();
88396             if (me.updateEl && me.boundEl) {
88397                 me.boundEl.update(value);
88398             }
88399             me.hideEdit(remainVisible);
88400             me.fireEvent('complete', me, value, me.startValue);
88401         }
88402     },
88403
88404     // private
88405     onShow : function() {
88406         var me = this;
88407
88408         me.callParent(arguments);
88409         if (me.hideEl !== false) {
88410             me.boundEl.hide();
88411         }
88412         me.fireEvent("startedit", me.boundEl, me.startValue);
88413     },
88414
88415     /**
88416      * Cancels the editing process and hides the editor without persisting any changes.  The field value will be
88417      * reverted to the original starting value.
88418      * @param {Boolean} [remainVisible=false] Override the default behavior and keep the editor visible after cancel
88419      */
88420     cancelEdit : function(remainVisible) {
88421         var me = this,
88422             startValue = me.startValue,
88423             value;
88424
88425         if (me.editing) {
88426             value = me.getValue();
88427             me.setValue(startValue);
88428             me.hideEdit(remainVisible);
88429             me.fireEvent('canceledit', me, value, startValue);
88430         }
88431     },
88432
88433     // private
88434     hideEdit: function(remainVisible) {
88435         if (remainVisible !== true) {
88436             this.editing = false;
88437             this.hide();
88438         }
88439     },
88440
88441     // private
88442     onBlur : function() {
88443         var me = this;
88444
88445         // selectSameEditor flag allows the same editor to be started without onBlur firing on itself
88446         if(me.allowBlur === true && me.editing && me.selectSameEditor !== true) {
88447             me.completeEdit();
88448         }
88449     },
88450
88451     // private
88452     onHide : function() {
88453         var me = this,
88454             field = me.field;
88455
88456         if (me.editing) {
88457             me.completeEdit();
88458             return;
88459         }
88460         field.blur();
88461         if (field.collapse) {
88462             field.collapse();
88463         }
88464
88465         //field.hide();
88466         if (me.hideEl !== false) {
88467             me.boundEl.show();
88468         }
88469         me.callParent(arguments);
88470     },
88471
88472     /**
88473      * Sets the data value of the editor
88474      * @param {Object} value Any valid value supported by the underlying field
88475      */
88476     setValue : function(value) {
88477         this.field.setValue(value);
88478     },
88479
88480     /**
88481      * Gets the data value of the editor
88482      * @return {Object} The data value
88483      */
88484     getValue : function() {
88485         return this.field.getValue();
88486     },
88487
88488     beforeDestroy : function() {
88489         var me = this;
88490
88491         Ext.destroy(me.field);
88492         delete me.field;
88493         delete me.parentEl;
88494         delete me.boundEl;
88495
88496         me.callParent(arguments);
88497     }
88498 });
88499 /**
88500  * @class Ext.Img
88501  * @extends Ext.Component
88502  *
88503  * Simple helper class for easily creating image components. This simply renders an image tag to the DOM
88504  * with the configured src.
88505  *
88506  * {@img Ext.Img/Ext.Img.png Ext.Img component}
88507  *
88508  * ## Example usage: 
88509  *
88510  *     var changingImage = Ext.create('Ext.Img', {
88511  *         src: 'http://www.sencha.com/img/20110215-feat-html5.png',
88512  *         renderTo: Ext.getBody()
88513  *     });
88514  *      
88515  *     // change the src of the image programmatically
88516  *     changingImage.setSrc('http://www.sencha.com/img/20110215-feat-perf.png');
88517 */
88518 Ext.define('Ext.Img', {
88519     extend: 'Ext.Component',
88520     alias: ['widget.image', 'widget.imagecomponent'],
88521     /** @cfg {String} src The image src */
88522     src: '',
88523
88524     getElConfig: function() {
88525         return {
88526             tag: 'img',
88527             src: this.src
88528         };
88529     },
88530     
88531     // null out this function, we can't set any html inside the image
88532     initRenderTpl: Ext.emptyFn,
88533     
88534     /**
88535      * Updates the {@link #src} of the image
88536      */
88537     setSrc: function(src) {
88538         var me = this,
88539             img = me.el;
88540         me.src = src;
88541         if (img) {
88542             img.dom.src = src;
88543         }
88544     }
88545 });
88546
88547 /**
88548  * @class Ext.Layer
88549  * @extends Ext.Element
88550  * An extended {@link Ext.Element} object that supports a shadow and shim, constrain to viewport and
88551  * automatic maintaining of shadow/shim positions.
88552  *
88553  * @cfg {Boolean} [shim=true]
88554  * False to disable the iframe shim in browsers which need one.
88555  *
88556  * @cfg {String/Boolean} [shadow=false]
88557  * True to automatically create an {@link Ext.Shadow}, or a string indicating the
88558  * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow.
88559  *
88560  * @cfg {Object} [dh={tag: 'div', cls: 'x-layer'}]
88561  * DomHelper object config to create element with.
88562  *
88563  * @cfg {Boolean} [constrain=true]
88564  * False to disable constrain to viewport.
88565  *
88566  * @cfg {String} cls
88567  * CSS class to add to the element
88568  *
88569  * @cfg {Number} [zindex=11000]
88570  * Starting z-index.
88571  *
88572  * @cfg {Number} [shadowOffset=4]
88573  * Number of pixels to offset the shadow
88574  *
88575  * @cfg {Boolean} [useDisplay=false]
88576  * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
88577  * to use css style <tt>'display:none;'</tt> to hide the Layer.
88578  *
88579  * @cfg {String} visibilityCls
88580  * The CSS class name to add in order to hide this Layer if this layer
88581  * is configured with <code>{@link #hideMode}: 'asclass'</code>
88582  *
88583  * @cfg {String} hideMode
88584  * A String which specifies how this Layer will be hidden.
88585  * Values may be<div class="mdetail-params"><ul>
88586  * <li><code>'display'</code> : The Component will be hidden using the <code>display: none</code> style.</li>
88587  * <li><code>'visibility'</code> : The Component will be hidden using the <code>visibility: hidden</code> style.</li>
88588  * <li><code>'offsets'</code> : The Component will be hidden by absolutely positioning it out of the visible area of the document. This
88589  * is useful when a hidden Component must maintain measurable dimensions. Hiding using <code>display</code> results
88590  * in a Component having zero dimensions.</li></ul></div>
88591  */
88592 Ext.define('Ext.Layer', {
88593     uses: ['Ext.Shadow'],
88594
88595     // shims are shared among layer to keep from having 100 iframes
88596     statics: {
88597         shims: []
88598     },
88599
88600     extend: 'Ext.Element',
88601
88602     /**
88603      * Creates new Layer.
88604      * @param {Object} config (optional) An object with config options.
88605      * @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element.
88606      * If the element is not found it creates it.
88607      */
88608     constructor: function(config, existingEl) {
88609         config = config || {};
88610         var me = this,
88611             dh = Ext.DomHelper,
88612             cp = config.parentEl,
88613             pel = cp ? Ext.getDom(cp) : document.body,
88614         hm = config.hideMode;
88615
88616         if (existingEl) {
88617             me.dom = Ext.getDom(existingEl);
88618         }
88619         if (!me.dom) {
88620             me.dom = dh.append(pel, config.dh || {
88621                 tag: 'div',
88622                 cls: Ext.baseCSSPrefix + 'layer'
88623             });
88624         } else {
88625             me.addCls(Ext.baseCSSPrefix + 'layer');
88626             if (!me.dom.parentNode) {
88627                 pel.appendChild(me.dom);
88628             }
88629         }
88630
88631         if (config.cls) {
88632             me.addCls(config.cls);
88633         }
88634         me.constrain = config.constrain !== false;
88635
88636         // Allow Components to pass their hide mode down to the Layer if they are floating.
88637         // Otherwise, allow useDisplay to override the default hiding method which is visibility.
88638         // TODO: Have ExtJS's Element implement visibilityMode by using classes as in Mobile.
88639         if (hm) {
88640             me.setVisibilityMode(Ext.Element[hm.toUpperCase()]);
88641             if (me.visibilityMode == Ext.Element.ASCLASS) {
88642                 me.visibilityCls = config.visibilityCls;
88643             }
88644         } else if (config.useDisplay) {
88645             me.setVisibilityMode(Ext.Element.DISPLAY);
88646         } else {
88647             me.setVisibilityMode(Ext.Element.VISIBILITY);
88648         }
88649
88650         if (config.id) {
88651             me.id = me.dom.id = config.id;
88652         } else {
88653             me.id = Ext.id(me.dom);
88654         }
88655         me.position('absolute');
88656         if (config.shadow) {
88657             me.shadowOffset = config.shadowOffset || 4;
88658             me.shadow = Ext.create('Ext.Shadow', {
88659                 offset: me.shadowOffset,
88660                 mode: config.shadow
88661             });
88662             me.disableShadow();
88663         } else {
88664             me.shadowOffset = 0;
88665         }
88666         me.useShim = config.shim !== false && Ext.useShims;
88667         if (config.hidden === true) {
88668             me.hide();
88669         } else {
88670             me.show();
88671         }
88672     },
88673
88674     getZIndex: function() {
88675         return parseInt((this.getShim() || this).getStyle('z-index'), 10);
88676     },
88677
88678     getShim: function() {
88679         var me = this,
88680             shim, pn;
88681
88682         if (!me.useShim) {
88683             return null;
88684         }
88685         if (!me.shim) {
88686             shim = me.self.shims.shift();
88687             if (!shim) {
88688                 shim = me.createShim();
88689                 shim.enableDisplayMode('block');
88690                 shim.hide();
88691             }
88692             pn = me.dom.parentNode;
88693             if (shim.dom.parentNode != pn) {
88694                 pn.insertBefore(shim.dom, me.dom);
88695             }
88696             me.shim = shim;
88697         }
88698         return me.shim;
88699     },
88700
88701     hideShim: function() {
88702         var me = this;
88703         
88704         if (me.shim) {
88705             me.shim.setDisplayed(false);
88706             me.self.shims.push(me.shim);
88707             delete me.shim;
88708         }
88709     },
88710
88711     disableShadow: function() {
88712         var me = this;
88713         
88714         if (me.shadow && !me.shadowDisabled) {
88715             me.shadowDisabled = true;
88716             me.shadow.hide();
88717             me.lastShadowOffset = me.shadowOffset;
88718             me.shadowOffset = 0;
88719         }
88720     },
88721
88722     enableShadow: function(show) {
88723         var me = this;
88724         
88725         if (me.shadow && me.shadowDisabled) {
88726             me.shadowDisabled = false;
88727             me.shadowOffset = me.lastShadowOffset;
88728             delete me.lastShadowOffset;
88729             if (show) {
88730                 me.sync(true);
88731             }
88732         }
88733     },
88734
88735     /**
88736      * @private
88737      * <p>Synchronize this Layer's associated elements, the shadow, and possibly the shim.</p>
88738      * <p>This code can execute repeatedly in milliseconds,
88739      * eg: dragging a Component configured liveDrag: true, or which has no ghost method
88740      * so code size was sacrificed for efficiency (e.g. no getBox/setBox, no XY calls)</p>
88741      * @param {Boolean} doShow Pass true to ensure that the shadow is shown.
88742      */
88743     sync: function(doShow) {
88744         var me = this,
88745             shadow = me.shadow,
88746             shadowPos, shimStyle, shadowSize;
88747
88748         if (!me.updating && me.isVisible() && (shadow || me.useShim)) {
88749             var shim = me.getShim(),
88750                 l = me.getLeft(true),
88751                 t = me.getTop(true),
88752                 w = me.dom.offsetWidth,
88753                 h = me.dom.offsetHeight,
88754                 shimIndex;
88755
88756             if (shadow && !me.shadowDisabled) {
88757                 if (doShow && !shadow.isVisible()) {
88758                     shadow.show(me);
88759                 } else {
88760                     shadow.realign(l, t, w, h);
88761                 }
88762                 if (shim) {
88763                     // TODO: Determine how the shims zIndex is above the layer zIndex at this point
88764                     shimIndex = shim.getStyle('z-index');
88765                     if (shimIndex > me.zindex) {
88766                         me.shim.setStyle('z-index', me.zindex - 2);
88767                     }
88768                     shim.show();
88769                     // fit the shim behind the shadow, so it is shimmed too
88770                     if (shadow.isVisible()) {
88771                         shadowPos = shadow.el.getXY();
88772                         shimStyle = shim.dom.style;
88773                         shadowSize = shadow.el.getSize();
88774                         if (Ext.supports.CSS3BoxShadow) {
88775                             shadowSize.height += 6;
88776                             shadowSize.width += 4;
88777                             shadowPos[0] -= 2;
88778                             shadowPos[1] -= 4;
88779                         }
88780                         shimStyle.left = (shadowPos[0]) + 'px';
88781                         shimStyle.top = (shadowPos[1]) + 'px';
88782                         shimStyle.width = (shadowSize.width) + 'px';
88783                         shimStyle.height = (shadowSize.height) + 'px';
88784                     } else {
88785                         shim.setSize(w, h);
88786                         shim.setLeftTop(l, t);
88787                     }
88788                 }
88789             } else if (shim) {
88790                 // TODO: Determine how the shims zIndex is above the layer zIndex at this point
88791                 shimIndex = shim.getStyle('z-index');
88792                 if (shimIndex > me.zindex) {
88793                     me.shim.setStyle('z-index', me.zindex - 2);
88794                 }
88795                 shim.show();
88796                 shim.setSize(w, h);
88797                 shim.setLeftTop(l, t);
88798             }
88799         }
88800         return me;
88801     },
88802
88803     remove: function() {
88804         this.hideUnders();
88805         this.callParent();
88806     },
88807
88808     // private
88809     beginUpdate: function() {
88810         this.updating = true;
88811     },
88812
88813     // private
88814     endUpdate: function() {
88815         this.updating = false;
88816         this.sync(true);
88817     },
88818
88819     // private
88820     hideUnders: function() {
88821         if (this.shadow) {
88822             this.shadow.hide();
88823         }
88824         this.hideShim();
88825     },
88826
88827     // private
88828     constrainXY: function() {
88829         if (this.constrain) {
88830             var vw = Ext.Element.getViewWidth(),
88831                 vh = Ext.Element.getViewHeight(),
88832                 s = Ext.getDoc().getScroll(),
88833                 xy = this.getXY(),
88834                 x = xy[0],
88835                 y = xy[1],
88836                 so = this.shadowOffset,
88837                 w = this.dom.offsetWidth + so,
88838                 h = this.dom.offsetHeight + so,
88839                 moved = false; // only move it if it needs it
88840             // first validate right/bottom
88841             if ((x + w) > vw + s.left) {
88842                 x = vw - w - so;
88843                 moved = true;
88844             }
88845             if ((y + h) > vh + s.top) {
88846                 y = vh - h - so;
88847                 moved = true;
88848             }
88849             // then make sure top/left isn't negative
88850             if (x < s.left) {
88851                 x = s.left;
88852                 moved = true;
88853             }
88854             if (y < s.top) {
88855                 y = s.top;
88856                 moved = true;
88857             }
88858             if (moved) {
88859                 Ext.Layer.superclass.setXY.call(this, [x, y]);
88860                 this.sync();
88861             }
88862         }
88863         return this;
88864     },
88865
88866     getConstrainOffset: function() {
88867         return this.shadowOffset;
88868     },
88869
88870     // overridden Element method
88871     setVisible: function(visible, animate, duration, callback, easing) {
88872         var me = this,
88873             cb;
88874
88875         // post operation processing
88876         cb = function() {
88877             if (visible) {
88878                 me.sync(true);
88879             }
88880             if (callback) {
88881                 callback();
88882             }
88883         };
88884
88885         // Hide shadow and shim if hiding
88886         if (!visible) {
88887             me.hideUnders(true);
88888         }
88889         me.callParent([visible, animate, duration, callback, easing]);
88890         if (!animate) {
88891             cb();
88892         }
88893         return me;
88894     },
88895
88896     // private
88897     beforeFx: function() {
88898         this.beforeAction();
88899         return this.callParent(arguments);
88900     },
88901
88902     // private
88903     afterFx: function() {
88904         this.callParent(arguments);
88905         this.sync(this.isVisible());
88906     },
88907
88908     // private
88909     beforeAction: function() {
88910         if (!this.updating && this.shadow) {
88911             this.shadow.hide();
88912         }
88913     },
88914
88915     // overridden Element method
88916     setLeft: function(left) {
88917         this.callParent(arguments);
88918         return this.sync();
88919     },
88920
88921     setTop: function(top) {
88922         this.callParent(arguments);
88923         return this.sync();
88924     },
88925
88926     setLeftTop: function(left, top) {
88927         this.callParent(arguments);
88928         return this.sync();
88929     },
88930
88931     setXY: function(xy, animate, duration, callback, easing) {
88932         var me = this;
88933         
88934         // Callback will restore shadow state and call the passed callback
88935         callback = me.createCB(callback);
88936
88937         me.fixDisplay();
88938         me.beforeAction();
88939         me.callParent([xy, animate, duration, callback, easing]);
88940         if (!animate) {
88941             callback();
88942         }
88943         return me;
88944     },
88945
88946     // private
88947     createCB: function(callback) {
88948         var me = this,
88949             showShadow = me.shadow && me.shadow.isVisible();
88950
88951         return function() {
88952             me.constrainXY();
88953             me.sync(showShadow);
88954             if (callback) {
88955                 callback();
88956             }
88957         };
88958     },
88959
88960     // overridden Element method
88961     setX: function(x, animate, duration, callback, easing) {
88962         this.setXY([x, this.getY()], animate, duration, callback, easing);
88963         return this;
88964     },
88965
88966     // overridden Element method
88967     setY: function(y, animate, duration, callback, easing) {
88968         this.setXY([this.getX(), y], animate, duration, callback, easing);
88969         return this;
88970     },
88971
88972     // overridden Element method
88973     setSize: function(w, h, animate, duration, callback, easing) {
88974         var me = this;
88975         
88976         // Callback will restore shadow state and call the passed callback
88977         callback = me.createCB(callback);
88978
88979         me.beforeAction();
88980         me.callParent([w, h, animate, duration, callback, easing]);
88981         if (!animate) {
88982             callback();
88983         }
88984         return me;
88985     },
88986
88987     // overridden Element method
88988     setWidth: function(w, animate, duration, callback, easing) {
88989         var me = this;
88990         
88991         // Callback will restore shadow state and call the passed callback
88992         callback = me.createCB(callback);
88993
88994         me.beforeAction();
88995         me.callParent([w, animate, duration, callback, easing]);
88996         if (!animate) {
88997             callback();
88998         }
88999         return me;
89000     },
89001
89002     // overridden Element method
89003     setHeight: function(h, animate, duration, callback, easing) {
89004         var me = this;
89005         
89006         // Callback will restore shadow state and call the passed callback
89007         callback = me.createCB(callback);
89008
89009         me.beforeAction();
89010         me.callParent([h, animate, duration, callback, easing]);
89011         if (!animate) {
89012             callback();
89013         }
89014         return me;
89015     },
89016
89017     // overridden Element method
89018     setBounds: function(x, y, width, height, animate, duration, callback, easing) {
89019         var me = this;
89020         
89021         // Callback will restore shadow state and call the passed callback
89022         callback = me.createCB(callback);
89023
89024         me.beforeAction();
89025         if (!animate) {
89026             Ext.Layer.superclass.setXY.call(me, [x, y]);
89027             Ext.Layer.superclass.setSize.call(me, width, height);
89028             callback();
89029         } else {
89030             me.callParent([x, y, width, height, animate, duration, callback, easing]);
89031         }
89032         return me;
89033     },
89034
89035     /**
89036      * <p>Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
89037      * incremented depending upon the presence of a shim or a shadow in so that it always shows above those two associated elements.</p>
89038      * <p>Any shim, will be assigned the passed z-index. A shadow will be assigned the next highet z-index, and the Layer's
89039      * element will receive the highest  z-index.
89040      * @param {Number} zindex The new z-index to set
89041      * @return {Ext.Layer} The Layer
89042      */
89043     setZIndex: function(zindex) {
89044         var me = this;
89045         
89046         me.zindex = zindex;
89047         if (me.getShim()) {
89048             me.shim.setStyle('z-index', zindex++);
89049         }
89050         if (me.shadow) {
89051             me.shadow.setZIndex(zindex++);
89052         }
89053         return me.setStyle('z-index', zindex);
89054     },
89055     
89056     setOpacity: function(opacity){
89057         if (this.shadow) {
89058             this.shadow.setOpacity(opacity);
89059         }
89060         return this.callParent(arguments);
89061     }
89062 });
89063
89064 /**
89065  * @class Ext.layout.component.ProgressBar
89066  * @extends Ext.layout.component.Component
89067  * @private
89068  */
89069
89070 Ext.define('Ext.layout.component.ProgressBar', {
89071
89072     /* Begin Definitions */
89073
89074     alias: ['layout.progressbar'],
89075
89076     extend: 'Ext.layout.component.Component',
89077
89078     /* End Definitions */
89079
89080     type: 'progressbar',
89081
89082     onLayout: function(width, height) {
89083         var me = this,
89084             owner = me.owner,
89085             textEl = owner.textEl;
89086         
89087         me.setElementSize(owner.el, width, height);
89088         textEl.setWidth(owner.el.getWidth(true));
89089         
89090         me.callParent([width, height]);
89091         
89092         owner.updateProgress(owner.value);
89093     }
89094 });
89095 /**
89096  * An updateable progress bar component. The progress bar supports two different modes: manual and automatic.
89097  *
89098  * In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the progress bar
89099  * as needed from your own code. This method is most appropriate when you want to show progress throughout an operation
89100  * that has predictable points of interest at which you can update the control.
89101  *
89102  * In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it once the
89103  * operation is complete. You can optionally have the progress bar wait for a specific amount of time and then clear
89104  * itself. Automatic mode is most appropriate for timed operations or asynchronous operations in which you have no need
89105  * for indicating intermediate progress.
89106  *
89107  *     @example
89108  *     var p = Ext.create('Ext.ProgressBar', {
89109  *        renderTo: Ext.getBody(),
89110  *        width: 300
89111  *     });
89112  *
89113  *     // Wait for 5 seconds, then update the status el (progress bar will auto-reset)
89114  *     p.wait({
89115  *         interval: 500, //bar will move fast!
89116  *         duration: 50000,
89117  *         increment: 15,
89118  *         text: 'Updating...',
89119  *         scope: this,
89120  *         fn: function(){
89121  *             p.updateText('Done!');
89122  *         }
89123  *     });
89124  */
89125 Ext.define('Ext.ProgressBar', {
89126     extend: 'Ext.Component',
89127     alias: 'widget.progressbar',
89128
89129     requires: [
89130         'Ext.Template',
89131         'Ext.CompositeElement',
89132         'Ext.TaskManager',
89133         'Ext.layout.component.ProgressBar'
89134     ],
89135
89136     uses: ['Ext.fx.Anim'],
89137
89138    /**
89139     * @cfg {Number} [value=0]
89140     * A floating point value between 0 and 1 (e.g., .5)
89141     */
89142
89143    /**
89144     * @cfg {String} [text='']
89145     * The progress bar text (defaults to '')
89146     */
89147
89148    /**
89149     * @cfg {String/HTMLElement/Ext.Element} textEl
89150     * The element to render the progress text to (defaults to the progress bar's internal text element)
89151     */
89152
89153    /**
89154     * @cfg {String} id
89155     * The progress bar element's id (defaults to an auto-generated id)
89156     */
89157
89158    /**
89159     * @cfg {String} [baseCls='x-progress']
89160     * The base CSS class to apply to the progress bar's wrapper element.
89161     */
89162     baseCls: Ext.baseCSSPrefix + 'progress',
89163
89164     config: {
89165         /**
89166         * @cfg {Boolean} animate
89167         * True to animate the progress bar during transitions
89168         */
89169         animate: false,
89170
89171         /**
89172          * @cfg {String} text
89173          * The text shown in the progress bar
89174          */
89175         text: ''
89176     },
89177
89178     // private
89179     waitTimer: null,
89180
89181     renderTpl: [
89182         '<div class="{baseCls}-text {baseCls}-text-back">',
89183             '<div>&#160;</div>',
89184         '</div>',
89185         '<div id="{id}-bar" class="{baseCls}-bar">',
89186             '<div class="{baseCls}-text">',
89187                 '<div>&#160;</div>',
89188             '</div>',
89189         '</div>'
89190     ],
89191
89192     componentLayout: 'progressbar',
89193
89194     // private
89195     initComponent: function() {
89196         this.callParent();
89197
89198         this.addChildEls('bar');
89199
89200         this.addEvents(
89201             /**
89202              * @event update
89203              * Fires after each update interval
89204              * @param {Ext.ProgressBar} this
89205              * @param {Number} value The current progress value
89206              * @param {String} text The current progress text
89207              */
89208             "update"
89209         );
89210     },
89211
89212     afterRender : function() {
89213         var me = this;
89214
89215         // This produces a composite w/2 el's (which is why we cannot use childEls or
89216         // renderSelectors):
89217         me.textEl = me.textEl ? Ext.get(me.textEl) : me.el.select('.' + me.baseCls + '-text');
89218
89219         me.callParent(arguments);
89220
89221         if (me.value) {
89222             me.updateProgress(me.value, me.text);
89223         }
89224         else {
89225             me.updateText(me.text);
89226         }
89227     },
89228
89229     /**
89230      * Updates the progress bar value, and optionally its text. If the text argument is not specified, any existing text
89231      * value will be unchanged. To blank out existing text, pass ''. Note that even if the progress bar value exceeds 1,
89232      * it will never automatically reset -- you are responsible for determining when the progress is complete and
89233      * calling {@link #reset} to clear and/or hide the control.
89234      * @param {Number} [value=0] A floating point value between 0 and 1 (e.g., .5)
89235      * @param {String} [text=''] The string to display in the progress text element
89236      * @param {Boolean} [animate=false] Whether to animate the transition of the progress bar. If this value is not
89237      * specified, the default for the class is used
89238      * @return {Ext.ProgressBar} this
89239      */
89240     updateProgress: function(value, text, animate) {
89241         var me = this,
89242             newWidth;
89243             
89244         me.value = value || 0;
89245         if (text) {
89246             me.updateText(text);
89247         }
89248         if (me.rendered && !me.isDestroyed) {
89249             if (me.isVisible(true)) {
89250                 newWidth = Math.floor(me.value * me.el.getWidth(true));
89251                 if (Ext.isForcedBorderBox) {
89252                     newWidth += me.bar.getBorderWidth("lr");
89253                 }
89254                 if (animate === true || (animate !== false && me.animate)) {
89255                     me.bar.stopAnimation();
89256                     me.bar.animate(Ext.apply({
89257                         to: {
89258                             width: newWidth + 'px'
89259                         }
89260                     }, me.animate));
89261                 } else {
89262                     me.bar.setWidth(newWidth);
89263                 }
89264             } else {
89265                 // force a layout when we're visible again
89266                 me.doComponentLayout();
89267             }
89268         }
89269         me.fireEvent('update', me, me.value, text);
89270         return me;
89271     },
89272
89273     /**
89274      * Updates the progress bar text. If specified, textEl will be updated, otherwise the progress bar itself will
89275      * display the updated text.
89276      * @param {String} [text=''] The string to display in the progress text element
89277      * @return {Ext.ProgressBar} this
89278      */
89279     updateText: function(text) {
89280         var me = this;
89281         
89282         me.text = text;
89283         if (me.rendered) {
89284             me.textEl.update(me.text);
89285         }
89286         return me;
89287     },
89288
89289     applyText : function(text) {
89290         this.updateText(text);
89291     },
89292
89293     /**
89294      * Initiates an auto-updating progress bar. A duration can be specified, in which case the progress bar will
89295      * automatically reset after a fixed amount of time and optionally call a callback function if specified. If no
89296      * duration is passed in, then the progress bar will run indefinitely and must be manually cleared by calling
89297      * {@link #reset}.
89298      *
89299      * Example usage:
89300      *
89301      *     var p = new Ext.ProgressBar({
89302      *        renderTo: 'my-el'
89303      *     });
89304      *
89305      *     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
89306      *     var p = Ext.create('Ext.ProgressBar', {
89307      *        renderTo: Ext.getBody(),
89308      *        width: 300
89309      *     });
89310      *
89311      *     //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
89312      *     p.wait({
89313      *        interval: 500, //bar will move fast!
89314      *        duration: 50000,
89315      *        increment: 15,
89316      *        text: 'Updating...',
89317      *        scope: this,
89318      *        fn: function(){
89319      *           p.updateText('Done!');
89320      *        }
89321      *     });
89322      *
89323      *     //Or update indefinitely until some async action completes, then reset manually
89324      *     p.wait();
89325      *     myAction.on('complete', function(){
89326      *         p.reset();
89327      *         p.updateText('Done!');
89328      *     });
89329      *
89330      * @param {Object} config (optional) Configuration options
89331      * @param {Number} config.duration The length of time in milliseconds that the progress bar should
89332      * run before resetting itself (defaults to undefined, in which case it will run indefinitely
89333      * until reset is called)
89334      * @param {Number} config.interval The length of time in milliseconds between each progress update
89335      * (defaults to 1000 ms)
89336      * @param {Boolean} config.animate Whether to animate the transition of the progress bar. If this
89337      * value is not specified, the default for the class is used.
89338      * @param {Number} config.increment The number of progress update segments to display within the
89339      * progress bar (defaults to 10).  If the bar reaches the end and is still updating, it will
89340      * automatically wrap back to the beginning.
89341      * @param {String} config.text Optional text to display in the progress bar element (defaults to '').
89342      * @param {Function} config.fn A callback function to execute after the progress bar finishes auto-
89343      * updating.  The function will be called with no arguments.  This function will be ignored if
89344      * duration is not specified since in that case the progress bar can only be stopped programmatically,
89345      * so any required function should be called by the same code after it resets the progress bar.
89346      * @param {Object} config.scope The scope that is passed to the callback function (only applies when
89347      * duration and fn are both passed).
89348      * @return {Ext.ProgressBar} this
89349      */
89350     wait: function(o) {
89351         var me = this;
89352             
89353         if (!me.waitTimer) {
89354             scope = me;
89355             o = o || {};
89356             me.updateText(o.text);
89357             me.waitTimer = Ext.TaskManager.start({
89358                 run: function(i){
89359                     var inc = o.increment || 10;
89360                     i -= 1;
89361                     me.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
89362                 },
89363                 interval: o.interval || 1000,
89364                 duration: o.duration,
89365                 onStop: function(){
89366                     if (o.fn) {
89367                         o.fn.apply(o.scope || me);
89368                     }
89369                     me.reset();
89370                 },
89371                 scope: scope
89372             });
89373         }
89374         return me;
89375     },
89376
89377     /**
89378      * Returns true if the progress bar is currently in a {@link #wait} operation
89379      * @return {Boolean} True if waiting, else false
89380      */
89381     isWaiting: function(){
89382         return this.waitTimer !== null;
89383     },
89384
89385     /**
89386      * Resets the progress bar value to 0 and text to empty string. If hide = true, the progress bar will also be hidden
89387      * (using the {@link #hideMode} property internally).
89388      * @param {Boolean} [hide=false] True to hide the progress bar.
89389      * @return {Ext.ProgressBar} this
89390      */
89391     reset: function(hide){
89392         var me = this;
89393         
89394         me.updateProgress(0);
89395         me.clearTimer();
89396         if (hide === true) {
89397             me.hide();
89398         }
89399         return me;
89400     },
89401
89402     // private
89403     clearTimer: function(){
89404         var me = this;
89405         
89406         if (me.waitTimer) {
89407             me.waitTimer.onStop = null; //prevent recursion
89408             Ext.TaskManager.stop(me.waitTimer);
89409             me.waitTimer = null;
89410         }
89411     },
89412
89413     onDestroy: function(){
89414         var me = this;
89415         
89416         me.clearTimer();
89417         if (me.rendered) {
89418             if (me.textEl.isComposite) {
89419                 me.textEl.clear();
89420             }
89421             Ext.destroyMembers(me, 'textEl', 'progressBar');
89422         }
89423         me.callParent();
89424     }
89425 });
89426
89427 /**
89428  * Private utility class that manages the internal Shadow cache
89429  * @private
89430  */
89431 Ext.define('Ext.ShadowPool', {
89432     singleton: true,
89433     requires: ['Ext.DomHelper'],
89434
89435     markup: function() {
89436         if (Ext.supports.CSS3BoxShadow) {
89437             return '<div class="' + Ext.baseCSSPrefix + 'css-shadow" role="presentation"></div>';
89438         } else if (Ext.isIE) {
89439             return '<div class="' + Ext.baseCSSPrefix + 'ie-shadow" role="presentation"></div>';
89440         } else {
89441             return '<div class="' + Ext.baseCSSPrefix + 'frame-shadow" role="presentation">' +
89442                 '<div class="xst" role="presentation">' +
89443                     '<div class="xstl" role="presentation"></div>' +
89444                     '<div class="xstc" role="presentation"></div>' +
89445                     '<div class="xstr" role="presentation"></div>' +
89446                 '</div>' +
89447                 '<div class="xsc" role="presentation">' +
89448                     '<div class="xsml" role="presentation"></div>' +
89449                     '<div class="xsmc" role="presentation"></div>' +
89450                     '<div class="xsmr" role="presentation"></div>' +
89451                 '</div>' +
89452                 '<div class="xsb" role="presentation">' +
89453                     '<div class="xsbl" role="presentation"></div>' +
89454                     '<div class="xsbc" role="presentation"></div>' +
89455                     '<div class="xsbr" role="presentation"></div>' +
89456                 '</div>' +
89457             '</div>';
89458         }
89459     }(),
89460
89461     shadows: [],
89462
89463     pull: function() {
89464         var sh = this.shadows.shift();
89465         if (!sh) {
89466             sh = Ext.get(Ext.DomHelper.insertHtml("beforeBegin", document.body.firstChild, this.markup));
89467             sh.autoBoxAdjust = false;
89468         }
89469         return sh;
89470     },
89471
89472     push: function(sh) {
89473         this.shadows.push(sh);
89474     },
89475     
89476     reset: function() {
89477         Ext.Array.each(this.shadows, function(shadow) {
89478             shadow.remove();
89479         });
89480         this.shadows = [];
89481     }
89482 });
89483 /**
89484  * @class Ext.Shadow
89485  * Simple class that can provide a shadow effect for any element.  Note that the element MUST be absolutely positioned,
89486  * and the shadow does not provide any shimming.  This should be used only in simple cases -- for more advanced
89487  * functionality that can also provide the same shadow effect, see the {@link Ext.Layer} class.
89488  */
89489 Ext.define('Ext.Shadow', {
89490     requires: ['Ext.ShadowPool'],
89491
89492     /**
89493      * Creates new Shadow.
89494      * @param {Object} config (optional) Config object.
89495      */
89496     constructor: function(config) {
89497         var me = this,
89498             adjusts = {
89499                 h: 0
89500             },
89501             offset,
89502             rad;
89503         
89504         Ext.apply(me, config);
89505         if (!Ext.isString(me.mode)) {
89506             me.mode = me.defaultMode;
89507         }
89508         offset = me.offset;
89509         rad = Math.floor(offset / 2);
89510         me.opacity = 50;
89511         switch (me.mode.toLowerCase()) {
89512             // all this hideous nonsense calculates the various offsets for shadows
89513             case "drop":
89514                 if (Ext.supports.CSS3BoxShadow) {
89515                     adjusts.w = adjusts.h = -offset;
89516                     adjusts.l = adjusts.t = offset;
89517                 } else {
89518                     adjusts.w = 0;
89519                     adjusts.l = adjusts.t = offset;
89520                     adjusts.t -= 1;
89521                     if (Ext.isIE) {
89522                         adjusts.l -= offset + rad;
89523                         adjusts.t -= offset + rad;
89524                         adjusts.w -= rad;
89525                         adjusts.h -= rad;
89526                         adjusts.t += 1;
89527                     }
89528                 }
89529                 break;
89530             case "sides":
89531                 if (Ext.supports.CSS3BoxShadow) {
89532                     adjusts.h -= offset;
89533                     adjusts.t = offset;
89534                     adjusts.l = adjusts.w = 0;
89535                 } else {
89536                     adjusts.w = (offset * 2);
89537                     adjusts.l = -offset;
89538                     adjusts.t = offset - 1;
89539                     if (Ext.isIE) {
89540                         adjusts.l -= (offset - rad);
89541                         adjusts.t -= offset + rad;
89542                         adjusts.l += 1;
89543                         adjusts.w -= (offset - rad) * 2;
89544                         adjusts.w -= rad + 1;
89545                         adjusts.h -= 1;
89546                     }
89547                 }
89548                 break;
89549             case "frame":
89550                 if (Ext.supports.CSS3BoxShadow) {
89551                     adjusts.l = adjusts.w = adjusts.t = 0;
89552                 } else {
89553                     adjusts.w = adjusts.h = (offset * 2);
89554                     adjusts.l = adjusts.t = -offset;
89555                     adjusts.t += 1;
89556                     adjusts.h -= 2;
89557                     if (Ext.isIE) {
89558                         adjusts.l -= (offset - rad);
89559                         adjusts.t -= (offset - rad);
89560                         adjusts.l += 1;
89561                         adjusts.w -= (offset + rad + 1);
89562                         adjusts.h -= (offset + rad);
89563                         adjusts.h += 1;
89564                     }
89565                     break;
89566                 }
89567         }
89568         me.adjusts = adjusts;
89569     },
89570
89571     /**
89572      * @cfg {String} mode
89573      * The shadow display mode.  Supports the following options:<div class="mdetail-params"><ul>
89574      * <li><b><tt>sides</tt></b> : Shadow displays on both sides and bottom only</li>
89575      * <li><b><tt>frame</tt></b> : Shadow displays equally on all four sides</li>
89576      * <li><b><tt>drop</tt></b> : Traditional bottom-right drop shadow</li>
89577      * </ul></div>
89578      */
89579     /**
89580      * @cfg {Number} offset
89581      * The number of pixels to offset the shadow from the element
89582      */
89583     offset: 4,
89584
89585     // private
89586     defaultMode: "drop",
89587
89588     /**
89589      * Displays the shadow under the target element
89590      * @param {String/HTMLElement/Ext.Element} targetEl The id or element under which the shadow should display
89591      */
89592     show: function(target) {
89593         var me = this,
89594             index;
89595         
89596         target = Ext.get(target);
89597         if (!me.el) {
89598             me.el = Ext.ShadowPool.pull();
89599             if (me.el.dom.nextSibling != target.dom) {
89600                 me.el.insertBefore(target);
89601             }
89602         }
89603         index = (parseInt(target.getStyle("z-index"), 10) - 1) || 0;
89604         me.el.setStyle("z-index", me.zIndex || index);
89605         if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
89606             me.el.dom.style.filter = "progid:DXImageTransform.Microsoft.alpha(opacity=" + me.opacity + ") progid:DXImageTransform.Microsoft.Blur(pixelradius=" + (me.offset) + ")";
89607         }
89608         me.realign(
89609             target.getLeft(true),
89610             target.getTop(true),
89611             target.dom.offsetWidth,
89612             target.dom.offsetHeight
89613         );
89614         me.el.dom.style.display = "block";
89615     },
89616
89617     /**
89618      * Returns true if the shadow is visible, else false
89619      */
89620     isVisible: function() {
89621         return this.el ? true: false;
89622     },
89623
89624     /**
89625      * Direct alignment when values are already available. Show must be called at least once before
89626      * calling this method to ensure it is initialized.
89627      * @param {Number} left The target element left position
89628      * @param {Number} top The target element top position
89629      * @param {Number} width The target element width
89630      * @param {Number} height The target element height
89631      */
89632     realign: function(l, t, targetWidth, targetHeight) {
89633         if (!this.el) {
89634             return;
89635         }
89636         var adjusts = this.adjusts,
89637             d = this.el.dom,
89638             targetStyle = d.style,
89639             shadowWidth,
89640             shadowHeight,
89641             cn,
89642             sww, 
89643             sws, 
89644             shs;
89645
89646         targetStyle.left = (l + adjusts.l) + "px";
89647         targetStyle.top = (t + adjusts.t) + "px";
89648         shadowWidth = Math.max(targetWidth + adjusts.w, 0);
89649         shadowHeight = Math.max(targetHeight + adjusts.h, 0);
89650         sws = shadowWidth + "px";
89651         shs = shadowHeight + "px";
89652         if (targetStyle.width != sws || targetStyle.height != shs) {
89653             targetStyle.width = sws;
89654             targetStyle.height = shs;
89655             if (Ext.supports.CSS3BoxShadow) {
89656                 targetStyle.boxShadow = '0 0 ' + this.offset + 'px 0 #888';
89657             } else {
89658
89659                 // Adjust the 9 point framed element to poke out on the required sides
89660                 if (!Ext.isIE) {
89661                     cn = d.childNodes;
89662                     sww = Math.max(0, (shadowWidth - 12)) + "px";
89663                     cn[0].childNodes[1].style.width = sww;
89664                     cn[1].childNodes[1].style.width = sww;
89665                     cn[2].childNodes[1].style.width = sww;
89666                     cn[1].style.height = Math.max(0, (shadowHeight - 12)) + "px";
89667                 }
89668             }
89669         }
89670     },
89671
89672     /**
89673      * Hides this shadow
89674      */
89675     hide: function() {
89676         var me = this;
89677         
89678         if (me.el) {
89679             me.el.dom.style.display = "none";
89680             Ext.ShadowPool.push(me.el);
89681             delete me.el;
89682         }
89683     },
89684
89685     /**
89686      * Adjust the z-index of this shadow
89687      * @param {Number} zindex The new z-index
89688      */
89689     setZIndex: function(z) {
89690         this.zIndex = z;
89691         if (this.el) {
89692             this.el.setStyle("z-index", z);
89693         }
89694     },
89695     
89696     /**
89697      * Sets the opacity of the shadow
89698      * @param {Number} opacity The opacity
89699      */
89700     setOpacity: function(opacity){
89701         if (this.el) {
89702             if (Ext.isIE && !Ext.supports.CSS3BoxShadow) {
89703                 opacity = Math.floor(opacity * 100 / 2) / 100;
89704             }
89705             this.opacity = opacity;
89706             this.el.setOpacity(opacity);
89707         }
89708     }
89709 });
89710 /**
89711  * A split button that provides a built-in dropdown arrow that can fire an event separately from the default click event
89712  * of the button. Typically this would be used to display a dropdown menu that provides additional options to the
89713  * primary button action, but any custom handler can provide the arrowclick implementation.  Example usage:
89714  *
89715  *     @example
89716  *     // display a dropdown menu:
89717  *     Ext.create('Ext.button.Split', {
89718  *         renderTo: Ext.getBody(),
89719  *         text: 'Options',
89720  *         // handle a click on the button itself
89721  *         handler: function() {
89722  *             alert("The button was clicked");
89723  *         },
89724  *         menu: new Ext.menu.Menu({
89725  *             items: [
89726  *                 // these will render as dropdown menu items when the arrow is clicked:
89727  *                 {text: 'Item 1', handler: function(){ alert("Item 1 clicked"); }},
89728  *                 {text: 'Item 2', handler: function(){ alert("Item 2 clicked"); }}
89729  *             ]
89730  *         })
89731  *     });
89732  *
89733  * Instead of showing a menu, you can provide any type of custom functionality you want when the dropdown
89734  * arrow is clicked:
89735  *
89736  *     Ext.create('Ext.button.Split', {
89737  *         renderTo: 'button-ct',
89738  *         text: 'Options',
89739  *         handler: optionsHandler,
89740  *         arrowHandler: myCustomHandler
89741  *     });
89742  *
89743  */
89744 Ext.define('Ext.button.Split', {
89745
89746     /* Begin Definitions */
89747     alias: 'widget.splitbutton',
89748
89749     extend: 'Ext.button.Button',
89750     alternateClassName: 'Ext.SplitButton',
89751     /* End Definitions */
89752     
89753     /**
89754      * @cfg {Function} arrowHandler
89755      * A function called when the arrow button is clicked (can be used instead of click event)
89756      */
89757     /**
89758      * @cfg {String} arrowTooltip
89759      * The title attribute of the arrow
89760      */
89761
89762     // private
89763     arrowCls      : 'split',
89764     split         : true,
89765
89766     // private
89767     initComponent : function(){
89768         this.callParent();
89769         /**
89770          * @event arrowclick
89771          * Fires when this button's arrow is clicked.
89772          * @param {Ext.button.Split} this
89773          * @param {Event} e The click event
89774          */
89775         this.addEvents("arrowclick");
89776     },
89777
89778     /**
89779      * Sets this button's arrow click handler.
89780      * @param {Function} handler The function to call when the arrow is clicked
89781      * @param {Object} scope (optional) Scope for the function passed above
89782      */
89783     setArrowHandler : function(handler, scope){
89784         this.arrowHandler = handler;
89785         this.scope = scope;
89786     },
89787
89788     // private
89789     onClick : function(e, t) {
89790         var me = this;
89791         
89792         e.preventDefault();
89793         if (!me.disabled) {
89794             if (me.overMenuTrigger) {
89795                 me.maybeShowMenu();
89796                 me.fireEvent("arrowclick", me, e);
89797                 if (me.arrowHandler) {
89798                     me.arrowHandler.call(me.scope || me, me, e);
89799                 }
89800             } else {
89801                 me.doToggle();
89802                 me.fireHandler();
89803             }
89804         }
89805     }
89806 });
89807 /**
89808  * A specialized SplitButton that contains a menu of {@link Ext.menu.CheckItem} elements. The button automatically
89809  * cycles through each menu item on click, raising the button's {@link #change} event (or calling the button's
89810  * {@link #changeHandler} function, if supplied) for the active menu item. Clicking on the arrow section of the
89811  * button displays the dropdown menu just like a normal SplitButton.  Example usage:
89812  *
89813  *     @example
89814  *     Ext.create('Ext.button.Cycle', {
89815  *         showText: true,
89816  *         prependText: 'View as ',
89817  *         renderTo: Ext.getBody(),
89818  *         menu: {
89819  *             id: 'view-type-menu',
89820  *             items: [{
89821  *                 text: 'text only',
89822  *                 iconCls: 'view-text',
89823  *                 checked: true
89824  *             },{
89825  *                 text: 'HTML',
89826  *                 iconCls: 'view-html'
89827  *             }]
89828  *         },
89829  *         changeHandler: function(cycleBtn, activeItem) {
89830  *             Ext.Msg.alert('Change View', activeItem.text);
89831  *         }
89832  *     });
89833  */
89834 Ext.define('Ext.button.Cycle', {
89835
89836     /* Begin Definitions */
89837
89838     alias: 'widget.cycle',
89839
89840     extend: 'Ext.button.Split',
89841     alternateClassName: 'Ext.CycleButton',
89842
89843     /* End Definitions */
89844
89845     /**
89846      * @cfg {Object[]} items
89847      * An array of {@link Ext.menu.CheckItem} **config** objects to be used when creating the button's menu items (e.g.,
89848      * `{text:'Foo', iconCls:'foo-icon'}`)
89849      * 
89850      * @deprecated 4.0 Use the {@link #menu} config instead. All menu items will be created as
89851      * {@link Ext.menu.CheckItem CheckItems}.
89852      */
89853     /**
89854      * @cfg {Boolean} [showText=false]
89855      * True to display the active item's text as the button text. The Button will show its
89856      * configured {@link #text} if this config is omitted.
89857      */
89858     /**
89859      * @cfg {String} [prependText='']
89860      * A static string to prepend before the active item's text when displayed as the button's text (only applies when
89861      * showText = true).
89862      */
89863     /**
89864      * @cfg {Function} changeHandler
89865      * A callback function that will be invoked each time the active menu item in the button's menu has changed. If this
89866      * callback is not supplied, the SplitButton will instead fire the {@link #change} event on active item change. The
89867      * changeHandler function will be called with the following argument list: (SplitButton this, Ext.menu.CheckItem
89868      * item)
89869      */
89870     /**
89871      * @cfg {String} forceIcon
89872      * A css class which sets an image to be used as the static icon for this button. This icon will always be displayed
89873      * regardless of which item is selected in the dropdown list. This overrides the default behavior of changing the
89874      * button's icon to match the selected item's icon on change.
89875      */
89876     /**
89877      * @property {Ext.menu.Menu} menu
89878      * The {@link Ext.menu.Menu Menu} object used to display the {@link Ext.menu.CheckItem CheckItems} representing the
89879      * available choices.
89880      */
89881
89882     // private
89883     getButtonText: function(item) {
89884         var me = this,
89885             text = '';
89886
89887         if (item && me.showText === true) {
89888             if (me.prependText) {
89889                 text += me.prependText;
89890             }
89891             text += item.text;
89892             return text;
89893         }
89894         return me.text;
89895     },
89896
89897     /**
89898      * Sets the button's active menu item.
89899      * @param {Ext.menu.CheckItem} item The item to activate
89900      * @param {Boolean} [suppressEvent=false] True to prevent the button's change event from firing.
89901      */
89902     setActiveItem: function(item, suppressEvent) {
89903         var me = this;
89904
89905         if (!Ext.isObject(item)) {
89906             item = me.menu.getComponent(item);
89907         }
89908         if (item) {
89909             if (!me.rendered) {
89910                 me.text = me.getButtonText(item);
89911                 me.iconCls = item.iconCls;
89912             } else {
89913                 me.setText(me.getButtonText(item));
89914                 me.setIconCls(item.iconCls);
89915             }
89916             me.activeItem = item;
89917             if (!item.checked) {
89918                 item.setChecked(true, false);
89919             }
89920             if (me.forceIcon) {
89921                 me.setIconCls(me.forceIcon);
89922             }
89923             if (!suppressEvent) {
89924                 me.fireEvent('change', me, item);
89925             }
89926         }
89927     },
89928
89929     /**
89930      * Gets the currently active menu item.
89931      * @return {Ext.menu.CheckItem} The active item
89932      */
89933     getActiveItem: function() {
89934         return this.activeItem;
89935     },
89936
89937     // private
89938     initComponent: function() {
89939         var me = this,
89940             checked = 0,
89941             items;
89942
89943         me.addEvents(
89944             /**
89945              * @event change
89946              * Fires after the button's active menu item has changed. Note that if a {@link #changeHandler} function is
89947              * set on this CycleButton, it will be called instead on active item change and this change event will not
89948              * be fired.
89949              * @param {Ext.button.Cycle} this
89950              * @param {Ext.menu.CheckItem} item The menu item that was selected
89951              */
89952             "change"
89953         );
89954
89955         if (me.changeHandler) {
89956             me.on('change', me.changeHandler, me.scope || me);
89957             delete me.changeHandler;
89958         }
89959
89960         // Allow them to specify a menu config which is a standard Button config.
89961         // Remove direct use of "items" in 5.0.
89962         items = (me.menu.items||[]).concat(me.items||[]);
89963         me.menu = Ext.applyIf({
89964             cls: Ext.baseCSSPrefix + 'cycle-menu',
89965             items: []
89966         }, me.menu);
89967
89968         // Convert all items to CheckItems
89969         Ext.each(items, function(item, i) {
89970             item = Ext.applyIf({
89971                 group: me.id,
89972                 itemIndex: i,
89973                 checkHandler: me.checkHandler,
89974                 scope: me,
89975                 checked: item.checked || false
89976             }, item);
89977             me.menu.items.push(item);
89978             if (item.checked) {
89979                 checked = i;
89980             }
89981         });
89982         me.itemCount = me.menu.items.length;
89983         me.callParent(arguments);
89984         me.on('click', me.toggleSelected, me);
89985         me.setActiveItem(checked, me);
89986
89987         // If configured with a fixed width, the cycling will center a different child item's text each click. Prevent this.
89988         if (me.width && me.showText) {
89989             me.addCls(Ext.baseCSSPrefix + 'cycle-fixed-width');
89990         }
89991     },
89992
89993     // private
89994     checkHandler: function(item, pressed) {
89995         if (pressed) {
89996             this.setActiveItem(item);
89997         }
89998     },
89999
90000     /**
90001      * This is normally called internally on button click, but can be called externally to advance the button's active
90002      * item programmatically to the next one in the menu. If the current item is the last one in the menu the active
90003      * item will be set to the first item in the menu.
90004      */
90005     toggleSelected: function() {
90006         var me = this,
90007             m = me.menu,
90008             checkItem;
90009
90010         checkItem = me.activeItem.next(':not([disabled])') || m.items.getAt(0);
90011         checkItem.setChecked(true);
90012     }
90013 });
90014 /**
90015  * Provides a container for arranging a group of related Buttons in a tabular manner.
90016  *
90017  *     @example
90018  *     Ext.create('Ext.panel.Panel', {
90019  *         title: 'Panel with ButtonGroup',
90020  *         width: 300,
90021  *         height:200,
90022  *         renderTo: document.body,
90023  *         bodyPadding: 10,
90024  *         html: 'HTML Panel Content',
90025  *         tbar: [{
90026  *             xtype: 'buttongroup',
90027  *             columns: 3,
90028  *             title: 'Clipboard',
90029  *             items: [{
90030  *                 text: 'Paste',
90031  *                 scale: 'large',
90032  *                 rowspan: 3,
90033  *                 iconCls: 'add',
90034  *                 iconAlign: 'top',
90035  *                 cls: 'btn-as-arrow'
90036  *             },{
90037  *                 xtype:'splitbutton',
90038  *                 text: 'Menu Button',
90039  *                 scale: 'large',
90040  *                 rowspan: 3,
90041  *                 iconCls: 'add',
90042  *                 iconAlign: 'top',
90043  *                 arrowAlign:'bottom',
90044  *                 menu: [{ text: 'Menu Item 1' }]
90045  *             },{
90046  *                 xtype:'splitbutton', text: 'Cut', iconCls: 'add16', menu: [{text: 'Cut Menu Item'}]
90047  *             },{
90048  *                 text: 'Copy', iconCls: 'add16'
90049  *             },{
90050  *                 text: 'Format', iconCls: 'add16'
90051  *             }]
90052  *         }]
90053  *     });
90054  *
90055  */
90056 Ext.define('Ext.container.ButtonGroup', {
90057     extend: 'Ext.panel.Panel',
90058     alias: 'widget.buttongroup',
90059     alternateClassName: 'Ext.ButtonGroup',
90060
90061     /**
90062      * @cfg {Number} columns The `columns` configuration property passed to the
90063      * {@link #layout configured layout manager}. See {@link Ext.layout.container.Table#columns}.
90064      */
90065
90066     /**
90067      * @cfg {String} baseCls  Defaults to <tt>'x-btn-group'</tt>.  See {@link Ext.panel.Panel#baseCls}.
90068      */
90069     baseCls: Ext.baseCSSPrefix + 'btn-group',
90070
90071     /**
90072      * @cfg {Object} layout  Defaults to <tt>'table'</tt>.  See {@link Ext.container.Container#layout}.
90073      */
90074     layout: {
90075         type: 'table'
90076     },
90077
90078     defaultType: 'button',
90079
90080     /**
90081      * @cfg {Boolean} frame  Defaults to <tt>true</tt>.  See {@link Ext.panel.Panel#frame}.
90082      */
90083     frame: true,
90084
90085     frameHeader: false,
90086
90087     internalDefaults: {removeMode: 'container', hideParent: true},
90088
90089     initComponent : function(){
90090         // Copy the component's columns config to the layout if specified
90091         var me = this,
90092             cols = me.columns;
90093
90094         me.noTitleCls = me.baseCls + '-notitle';
90095         if (cols) {
90096             me.layout = Ext.apply({}, {columns: cols}, me.layout);
90097         }
90098
90099         if (!me.title) {
90100             me.addCls(me.noTitleCls);
90101         }
90102         me.callParent(arguments);
90103     },
90104
90105     afterLayout: function() {
90106         var me = this;
90107
90108         me.callParent(arguments);
90109
90110         // Pugly hack for a pugly browser:
90111         // If not an explicitly set width, then size the width to match the inner table
90112         if (me.layout.table && (Ext.isIEQuirks || Ext.isIE6) && !me.width) {
90113             var t = me.getTargetEl();
90114             t.setWidth(me.layout.table.offsetWidth + t.getPadding('lr'));
90115         }
90116
90117         // IE7 needs a forced repaint to make the top framing div expand to full width
90118         if (Ext.isIE7) {
90119             me.el.repaint();
90120         }
90121     },
90122
90123     afterRender: function() {
90124         var me = this;
90125
90126         //we need to add an addition item in here so the ButtonGroup title is centered
90127         if (me.header) {
90128             // Header text cannot flex, but must be natural size if it's being centered
90129             delete me.header.items.items[0].flex;
90130
90131             // For Centering, surround the text with two flex:1 spacers.
90132             me.suspendLayout = true;
90133             me.header.insert(1, {
90134                 xtype: 'component',
90135                 ui   : me.ui,
90136                 flex : 1
90137             });
90138             me.header.insert(0, {
90139                 xtype: 'component',
90140                 ui   : me.ui,
90141                 flex : 1
90142             });
90143             me.suspendLayout = false;
90144         }
90145
90146         me.callParent(arguments);
90147     },
90148
90149     // private
90150     onBeforeAdd: function(component) {
90151         if (component.is('button')) {
90152             component.ui = component.ui + '-toolbar';
90153         }
90154         this.callParent(arguments);
90155     },
90156
90157     //private
90158     applyDefaults: function(c) {
90159         if (!Ext.isString(c)) {
90160             c = this.callParent(arguments);
90161             var d = this.internalDefaults;
90162             if (c.events) {
90163                 Ext.applyIf(c.initialConfig, d);
90164                 Ext.apply(c, d);
90165             } else {
90166                 Ext.applyIf(c, d);
90167             }
90168         }
90169         return c;
90170     }
90171
90172     /**
90173      * @cfg {Array} tools  @hide
90174      */
90175     /**
90176      * @cfg {Boolean} collapsible  @hide
90177      */
90178     /**
90179      * @cfg {Boolean} collapseMode  @hide
90180      */
90181     /**
90182      * @cfg {Boolean} animCollapse  @hide
90183      */
90184     /**
90185      * @cfg {Boolean} closable  @hide
90186      */
90187 });
90188
90189 /**
90190  * A specialized container representing the viewable application area (the browser viewport).
90191  *
90192  * The Viewport renders itself to the document body, and automatically sizes itself to the size of
90193  * the browser viewport and manages window resizing. There may only be one Viewport created
90194  * in a page.
90195  *
90196  * Like any {@link Ext.container.Container Container}, a Viewport will only perform sizing and positioning
90197  * on its child Components if you configure it with a {@link #layout}.
90198  *
90199  * A Common layout used with Viewports is {@link Ext.layout.container.Border border layout}, but if the
90200  * required layout is simpler, a different layout should be chosen.
90201  *
90202  * For example, to simply make a single child item occupy all available space, use
90203  * {@link Ext.layout.container.Fit fit layout}.
90204  *
90205  * To display one "active" item at full size from a choice of several child items, use
90206  * {@link Ext.layout.container.Card card layout}.
90207  *
90208  * Inner layouts are available by virtue of the fact that all {@link Ext.panel.Panel Panel}s
90209  * added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add}
90210  * method of any of its child Panels may themselves have a layout.
90211  *
90212  * The Viewport does not provide scrolling, so child Panels within the Viewport should provide
90213  * for scrolling if needed using the {@link #autoScroll} config.
90214  *
90215  * An example showing a classic application border layout:
90216  *
90217  *     @example
90218  *     Ext.create('Ext.container.Viewport', {
90219  *         layout: 'border',
90220  *         items: [{
90221  *             region: 'north',
90222  *             html: '<h1 class="x-panel-header">Page Title</h1>',
90223  *             autoHeight: true,
90224  *             border: false,
90225  *             margins: '0 0 5 0'
90226  *         }, {
90227  *             region: 'west',
90228  *             collapsible: true,
90229  *             title: 'Navigation',
90230  *             width: 150
90231  *             // could use a TreePanel or AccordionLayout for navigational items
90232  *         }, {
90233  *             region: 'south',
90234  *             title: 'South Panel',
90235  *             collapsible: true,
90236  *             html: 'Information goes here',
90237  *             split: true,
90238  *             height: 100,
90239  *             minHeight: 100
90240  *         }, {
90241  *             region: 'east',
90242  *             title: 'East Panel',
90243  *             collapsible: true,
90244  *             split: true,
90245  *             width: 150
90246  *         }, {
90247  *             region: 'center',
90248  *             xtype: 'tabpanel', // TabPanel itself has no title
90249  *             activeTab: 0,      // First tab active by default
90250  *             items: {
90251  *                 title: 'Default Tab',
90252  *                 html: 'The first tab\'s content. Others may be added dynamically'
90253  *             }
90254  *         }]
90255  *     });
90256  */
90257 Ext.define('Ext.container.Viewport', {
90258     extend: 'Ext.container.Container',
90259     alias: 'widget.viewport',
90260     requires: ['Ext.EventManager'],
90261     alternateClassName: 'Ext.Viewport',
90262
90263     // Privatize config options which, if used, would interfere with the
90264     // correct operation of the Viewport as the sole manager of the
90265     // layout of the document body.
90266
90267     /**
90268      * @cfg {String/HTMLElement/Ext.Element} applyTo
90269      * Not applicable.
90270      */
90271
90272     /**
90273      * @cfg {Boolean} allowDomMove
90274      * Not applicable.
90275      */
90276
90277     /**
90278      * @cfg {Boolean} hideParent
90279      * Not applicable.
90280      */
90281
90282     /**
90283      * @cfg {String/HTMLElement/Ext.Element} renderTo
90284      * Not applicable. Always renders to document body.
90285      */
90286
90287     /**
90288      * @cfg {Boolean} hideParent
90289      * Not applicable.
90290      */
90291
90292     /**
90293      * @cfg {Number} height
90294      * Not applicable. Sets itself to viewport width.
90295      */
90296
90297     /**
90298      * @cfg {Number} width
90299      * Not applicable. Sets itself to viewport height.
90300      */
90301
90302     /**
90303      * @cfg {Boolean} autoHeight
90304      * Not applicable.
90305      */
90306
90307     /**
90308      * @cfg {Boolean} autoWidth
90309      * Not applicable.
90310      */
90311
90312     /**
90313      * @cfg {Boolean} deferHeight
90314      * Not applicable.
90315      */
90316
90317     /**
90318      * @cfg {Boolean} monitorResize
90319      * Not applicable.
90320      */
90321
90322     isViewport: true,
90323
90324     ariaRole: 'application',
90325
90326     initComponent : function() {
90327         var me = this,
90328             html = Ext.fly(document.body.parentNode),
90329             el;
90330         me.callParent(arguments);
90331         html.addCls(Ext.baseCSSPrefix + 'viewport');
90332         if (me.autoScroll) {
90333             html.setStyle('overflow', 'auto');
90334         }
90335         me.el = el = Ext.getBody();
90336         el.setHeight = Ext.emptyFn;
90337         el.setWidth = Ext.emptyFn;
90338         el.setSize = Ext.emptyFn;
90339         el.dom.scroll = 'no';
90340         me.allowDomMove = false;
90341         Ext.EventManager.onWindowResize(me.fireResize, me);
90342         me.renderTo = me.el;
90343         me.width = Ext.Element.getViewportWidth();
90344         me.height = Ext.Element.getViewportHeight();
90345     },
90346
90347     fireResize : function(w, h){
90348         // setSize is the single entry point to layouts
90349         this.setSize(w, h);
90350     }
90351 });
90352
90353 /*
90354  * This is a derivative of the similarly named class in the YUI Library.
90355  * The original license:
90356  * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
90357  * Code licensed under the BSD License:
90358  * http://developer.yahoo.net/yui/license.txt
90359  */
90360
90361
90362 /**
90363  * @class Ext.dd.DDTarget
90364  * @extends Ext.dd.DragDrop
90365  * A DragDrop implementation that does not move, but can be a drop
90366  * target.  You would get the same result by simply omitting implementation
90367  * for the event callbacks, but this way we reduce the processing cost of the
90368  * event listener and the callbacks.
90369  */
90370 Ext.define('Ext.dd.DDTarget', {
90371     extend: 'Ext.dd.DragDrop',
90372
90373     /**
90374      * Creates new DDTarget.
90375      * @param {String} id the id of the element that is a drop target
90376      * @param {String} sGroup the group of related DragDrop objects
90377      * @param {Object} config an object containing configurable attributes.
90378      * Valid properties for DDTarget in addition to those in DragDrop: none.
90379      */
90380     constructor: function(id, sGroup, config) {
90381         if (id) {
90382             this.initTarget(id, sGroup, config);
90383         }
90384     },
90385
90386     /**
90387      * @hide
90388      * Overridden and disabled. A DDTarget does not support being dragged.
90389      * @method
90390      */
90391     getDragEl: Ext.emptyFn,
90392     /**
90393      * @hide
90394      * Overridden and disabled. A DDTarget does not support being dragged.
90395      * @method
90396      */
90397     isValidHandleChild: Ext.emptyFn,
90398     /**
90399      * @hide
90400      * Overridden and disabled. A DDTarget does not support being dragged.
90401      * @method
90402      */
90403     startDrag: Ext.emptyFn,
90404     /**
90405      * @hide
90406      * Overridden and disabled. A DDTarget does not support being dragged.
90407      * @method
90408      */
90409     endDrag: Ext.emptyFn,
90410     /**
90411      * @hide
90412      * Overridden and disabled. A DDTarget does not support being dragged.
90413      * @method
90414      */
90415     onDrag: Ext.emptyFn,
90416     /**
90417      * @hide
90418      * Overridden and disabled. A DDTarget does not support being dragged.
90419      * @method
90420      */
90421     onDragDrop: Ext.emptyFn,
90422     /**
90423      * @hide
90424      * Overridden and disabled. A DDTarget does not support being dragged.
90425      * @method
90426      */
90427     onDragEnter: Ext.emptyFn,
90428     /**
90429      * @hide
90430      * Overridden and disabled. A DDTarget does not support being dragged.
90431      * @method
90432      */
90433     onDragOut: Ext.emptyFn,
90434     /**
90435      * @hide
90436      * Overridden and disabled. A DDTarget does not support being dragged.
90437      * @method
90438      */
90439     onDragOver: Ext.emptyFn,
90440     /**
90441      * @hide
90442      * Overridden and disabled. A DDTarget does not support being dragged.
90443      * @method
90444      */
90445     onInvalidDrop: Ext.emptyFn,
90446     /**
90447      * @hide
90448      * Overridden and disabled. A DDTarget does not support being dragged.
90449      * @method
90450      */
90451     onMouseDown: Ext.emptyFn,
90452     /**
90453      * @hide
90454      * Overridden and disabled. A DDTarget does not support being dragged.
90455      * @method
90456      */
90457     onMouseUp: Ext.emptyFn,
90458     /**
90459      * @hide
90460      * Overridden and disabled. A DDTarget does not support being dragged.
90461      * @method
90462      */
90463     setXConstraint: Ext.emptyFn,
90464     /**
90465      * @hide
90466      * Overridden and disabled. A DDTarget does not support being dragged.
90467      * @method
90468      */
90469     setYConstraint: Ext.emptyFn,
90470     /**
90471      * @hide
90472      * Overridden and disabled. A DDTarget does not support being dragged.
90473      * @method
90474      */
90475     resetConstraints: Ext.emptyFn,
90476     /**
90477      * @hide
90478      * Overridden and disabled. A DDTarget does not support being dragged.
90479      * @method
90480      */
90481     clearConstraints: Ext.emptyFn,
90482     /**
90483      * @hide
90484      * Overridden and disabled. A DDTarget does not support being dragged.
90485      * @method
90486      */
90487     clearTicks: Ext.emptyFn,
90488     /**
90489      * @hide
90490      * Overridden and disabled. A DDTarget does not support being dragged.
90491      * @method
90492      */
90493     setInitPosition: Ext.emptyFn,
90494     /**
90495      * @hide
90496      * Overridden and disabled. A DDTarget does not support being dragged.
90497      * @method
90498      */
90499     setDragElId: Ext.emptyFn,
90500     /**
90501      * @hide
90502      * Overridden and disabled. A DDTarget does not support being dragged.
90503      * @method
90504      */
90505     setHandleElId: Ext.emptyFn,
90506     /**
90507      * @hide
90508      * Overridden and disabled. A DDTarget does not support being dragged.
90509      * @method
90510      */
90511     setOuterHandleElId: Ext.emptyFn,
90512     /**
90513      * @hide
90514      * Overridden and disabled. A DDTarget does not support being dragged.
90515      * @method
90516      */
90517     addInvalidHandleClass: Ext.emptyFn,
90518     /**
90519      * @hide
90520      * Overridden and disabled. A DDTarget does not support being dragged.
90521      * @method
90522      */
90523     addInvalidHandleId: Ext.emptyFn,
90524     /**
90525      * @hide
90526      * Overridden and disabled. A DDTarget does not support being dragged.
90527      * @method
90528      */
90529     addInvalidHandleType: Ext.emptyFn,
90530     /**
90531      * @hide
90532      * Overridden and disabled. A DDTarget does not support being dragged.
90533      * @method
90534      */
90535     removeInvalidHandleClass: Ext.emptyFn,
90536     /**
90537      * @hide
90538      * Overridden and disabled. A DDTarget does not support being dragged.
90539      * @method
90540      */
90541     removeInvalidHandleId: Ext.emptyFn,
90542     /**
90543      * @hide
90544      * Overridden and disabled. A DDTarget does not support being dragged.
90545      * @method
90546      */
90547     removeInvalidHandleType: Ext.emptyFn,
90548
90549     toString: function() {
90550         return ("DDTarget " + this.id);
90551     }
90552 });
90553 /**
90554  * @class Ext.dd.DragTracker
90555  * A DragTracker listens for drag events on an Element and fires events at the start and end of the drag,
90556  * as well as during the drag. This is useful for components such as {@link Ext.slider.Multi}, where there is
90557  * an element that can be dragged around to change the Slider's value.
90558  * DragTracker provides a series of template methods that should be overridden to provide functionality
90559  * in response to detected drag operations. These are onBeforeStart, onStart, onDrag and onEnd.
90560  * See {@link Ext.slider.Multi}'s initEvents function for an example implementation.
90561  */
90562 Ext.define('Ext.dd.DragTracker', {
90563
90564     uses: ['Ext.util.Region'],
90565
90566     mixins: {
90567         observable: 'Ext.util.Observable'
90568     },
90569
90570     /**
90571      * @property {Boolean} active
90572      * Read-only property indicated whether the user is currently dragging this
90573      * tracker.
90574      */
90575     active: false,
90576
90577     /**
90578      * @property {HTMLElement} dragTarget
90579      * <p><b>Only valid during drag operations. Read-only.</b></p>
90580      * <p>The element being dragged.</p>
90581      * <p>If the {@link #delegate} option is used, this will be the delegate element which was mousedowned.</p>
90582      */
90583
90584     /**
90585      * @cfg {Boolean} trackOver
90586      * <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>
90587      * <p>This is implicitly set when an {@link #overCls} is specified.</p>
90588      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
90589      */
90590     trackOver: false,
90591
90592     /**
90593      * @cfg {String} overCls
90594      * <p>A CSS class to add to the DragTracker's target element when the element (or, if the {@link #delegate} option is used,
90595      * when a delegate element) is mouseovered.</p>
90596      * <b>If the {@link #delegate} option is used, these events fire only when a delegate element is entered of left.</b>.
90597      */
90598
90599     /**
90600      * @cfg {Ext.util.Region/Ext.Element} constrainTo
90601      * <p>A {@link Ext.util.Region Region} (Or an element from which a Region measurement will be read) which is used to constrain
90602      * the result of the {@link #getOffset} call.</p>
90603      * <p>This may be set any time during the DragTracker's lifecycle to set a dynamic constraining region.</p>
90604      */
90605
90606     /**
90607      * @cfg {Number} tolerance
90608      * Number of pixels the drag target must be moved before dragging is
90609      * considered to have started. Defaults to <code>5</code>.
90610      */
90611     tolerance: 5,
90612
90613     /**
90614      * @cfg {Boolean/Number} autoStart
90615      * Defaults to <code>false</code>. Specify <code>true</code> to defer trigger start by 1000 ms.
90616      * Specify a Number for the number of milliseconds to defer trigger start.
90617      */
90618     autoStart: false,
90619
90620     /**
90621      * @cfg {String} delegate
90622      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the DragTracker's encapsulating
90623      * Element which are the tracked elements. This limits tracking to only begin when the matching elements are mousedowned.</p>
90624      * <p>This may also be a specific child element within the DragTracker's encapsulating element to use as the tracked element.</p>
90625      */
90626
90627     /**
90628      * @cfg {Boolean} preventDefault
90629      * Specify <code>false</code> to enable default actions on onMouseDown events. Defaults to <code>true</code>.
90630      */
90631
90632     /**
90633      * @cfg {Boolean} stopEvent
90634      * 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>.
90635      */
90636
90637     constructor : function(config){
90638         Ext.apply(this, config);
90639         this.addEvents(
90640             /**
90641              * @event mouseover <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
90642              * <p>Fires when the mouse enters the DragTracker's target element (or if {@link #delegate} is
90643              * used, when the mouse enters a delegate element).</p>
90644              * @param {Object} this
90645              * @param {Object} e event object
90646              * @param {HTMLElement} target The element mouseovered.
90647              */
90648             'mouseover',
90649
90650             /**
90651              * @event mouseout <p><b>Only available when {@link #trackOver} is <code>true</code></b></p>
90652              * <p>Fires when the mouse exits the DragTracker's target element (or if {@link #delegate} is
90653              * used, when the mouse exits a delegate element).</p>
90654              * @param {Object} this
90655              * @param {Object} e event object
90656              */
90657             'mouseout',
90658
90659             /**
90660              * @event mousedown <p>Fires when the mouse button is pressed down, but before a drag operation begins. The
90661              * drag operation begins after either the mouse has been moved by {@link #tolerance} pixels, or after
90662              * the {@link #autoStart} timer fires.</p>
90663              * <p>Return false to veto the drag operation.</p>
90664              * @param {Object} this
90665              * @param {Object} e event object
90666              */
90667             'mousedown',
90668
90669             /**
90670              * @event mouseup
90671              * @param {Object} this
90672              * @param {Object} e event object
90673              */
90674             'mouseup',
90675
90676             /**
90677              * @event mousemove Fired when the mouse is moved. Returning false cancels the drag operation.
90678              * @param {Object} this
90679              * @param {Object} e event object
90680              */
90681             'mousemove',
90682
90683             /**
90684              * @event beforestart
90685              * @param {Object} this
90686              * @param {Object} e event object
90687              */
90688             'beforedragstart',
90689
90690             /**
90691              * @event dragstart
90692              * @param {Object} this
90693              * @param {Object} e event object
90694              */
90695             'dragstart',
90696
90697             /**
90698              * @event dragend
90699              * @param {Object} this
90700              * @param {Object} e event object
90701              */
90702             'dragend',
90703
90704             /**
90705              * @event drag
90706              * @param {Object} this
90707              * @param {Object} e event object
90708              */
90709             'drag'
90710         );
90711
90712         this.dragRegion = Ext.create('Ext.util.Region', 0,0,0,0);
90713
90714         if (this.el) {
90715             this.initEl(this.el);
90716         }
90717
90718         // Dont pass the config so that it is not applied to 'this' again
90719         this.mixins.observable.constructor.call(this);
90720         if (this.disabled) {
90721             this.disable();
90722         }
90723
90724     },
90725
90726     /**
90727      * Initializes the DragTracker on a given element.
90728      * @param {Ext.Element/HTMLElement} el The element
90729      */
90730     initEl: function(el) {
90731         this.el = Ext.get(el);
90732
90733         // The delegate option may also be an element on which to listen
90734         this.handle = Ext.get(this.delegate);
90735
90736         // If delegate specified an actual element to listen on, we do not use the delegate listener option
90737         this.delegate = this.handle ? undefined : this.delegate;
90738
90739         if (!this.handle) {
90740             this.handle = this.el;
90741         }
90742
90743         // Add a mousedown listener which reacts only on the elements targeted by the delegate config.
90744         // We process mousedown to begin tracking.
90745         this.mon(this.handle, {
90746             mousedown: this.onMouseDown,
90747             delegate: this.delegate,
90748             scope: this
90749         });
90750
90751         // If configured to do so, track mouse entry and exit into the target (or delegate).
90752         // The mouseover and mouseout CANNOT be replaced with mouseenter and mouseleave
90753         // because delegate cannot work with those pseudoevents. Entry/exit checking is done in the handler.
90754         if (this.trackOver || this.overCls) {
90755             this.mon(this.handle, {
90756                 mouseover: this.onMouseOver,
90757                 mouseout: this.onMouseOut,
90758                 delegate: this.delegate,
90759                 scope: this
90760             });
90761         }
90762     },
90763
90764     disable: function() {
90765         this.disabled = true;
90766     },
90767
90768     enable: function() {
90769         this.disabled = false;
90770     },
90771
90772     destroy : function() {
90773         this.clearListeners();
90774         delete this.el;
90775     },
90776
90777     // When the pointer enters a tracking element, fire a mouseover if the mouse entered from outside.
90778     // This is mouseenter functionality, but we cannot use mouseenter because we are using "delegate" to filter mouse targets
90779     onMouseOver: function(e, target) {
90780         var me = this;
90781         if (!me.disabled) {
90782             if (Ext.EventManager.contains(e) || me.delegate) {
90783                 me.mouseIsOut = false;
90784                 if (me.overCls) {
90785                     me.el.addCls(me.overCls);
90786                 }
90787                 me.fireEvent('mouseover', me, e, me.delegate ? e.getTarget(me.delegate, target) : me.handle);
90788             }
90789         }
90790     },
90791
90792     // When the pointer exits a tracking element, fire a mouseout.
90793     // This is mouseleave functionality, but we cannot use mouseleave because we are using "delegate" to filter mouse targets
90794     onMouseOut: function(e) {
90795         if (this.mouseIsDown) {
90796             this.mouseIsOut = true;
90797         } else {
90798             if (this.overCls) {
90799                 this.el.removeCls(this.overCls);
90800             }
90801             this.fireEvent('mouseout', this, e);
90802         }
90803     },
90804
90805     onMouseDown: function(e, target){
90806         // If this is disabled, or the mousedown has been processed by an upstream DragTracker, return
90807         if (this.disabled ||e.dragTracked) {
90808             return;
90809         }
90810
90811         // This information should be available in mousedown listener and onBeforeStart implementations
90812         this.dragTarget = this.delegate ? target : this.handle.dom;
90813         this.startXY = this.lastXY = e.getXY();
90814         this.startRegion = Ext.fly(this.dragTarget).getRegion();
90815
90816         if (this.fireEvent('mousedown', this, e) === false ||
90817             this.fireEvent('beforedragstart', this, e) === false ||
90818             this.onBeforeStart(e) === false) {
90819             return;
90820         }
90821
90822         // Track when the mouse is down so that mouseouts while the mouse is down are not processed.
90823         // The onMouseOut method will only ever be called after mouseup.
90824         this.mouseIsDown = true;
90825
90826         // Flag for downstream DragTracker instances that the mouse is being tracked.
90827         e.dragTracked = true;
90828
90829         if (this.preventDefault !== false) {
90830             e.preventDefault();
90831         }
90832         Ext.getDoc().on({
90833             scope: this,
90834             mouseup: this.onMouseUp,
90835             mousemove: this.onMouseMove,
90836             selectstart: this.stopSelect
90837         });
90838         if (this.autoStart) {
90839             this.timer =  Ext.defer(this.triggerStart, this.autoStart === true ? 1000 : this.autoStart, this, [e]);
90840         }
90841     },
90842
90843     onMouseMove: function(e, target){
90844         // BrowserBug: IE hack to see if button was released outside of window.
90845         // Needed in IE6-9 in quirks and strictmode
90846         if (this.active && Ext.isIE && !e.browserEvent.button) {
90847             e.preventDefault();
90848             this.onMouseUp(e);
90849             return;
90850         }
90851
90852         e.preventDefault();
90853         var xy = e.getXY(),
90854             s = this.startXY;
90855
90856         this.lastXY = xy;
90857         if (!this.active) {
90858             if (Math.max(Math.abs(s[0]-xy[0]), Math.abs(s[1]-xy[1])) > this.tolerance) {
90859                 this.triggerStart(e);
90860             } else {
90861                 return;
90862             }
90863         }
90864
90865         // Returning false from a mousemove listener deactivates
90866         if (this.fireEvent('mousemove', this, e) === false) {
90867             this.onMouseUp(e);
90868         } else {
90869             this.onDrag(e);
90870             this.fireEvent('drag', this, e);
90871         }
90872     },
90873
90874     onMouseUp: function(e) {
90875         // Clear the flag which ensures onMouseOut fires only after the mouse button
90876         // is lifted if the mouseout happens *during* a drag.
90877         this.mouseIsDown = false;
90878
90879         // If we mouseouted the el *during* the drag, the onMouseOut method will not have fired. Ensure that it gets processed.
90880         if (this.mouseIsOut) {
90881             this.mouseIsOut = false;
90882             this.onMouseOut(e);
90883         }
90884         e.preventDefault();
90885         this.fireEvent('mouseup', this, e);
90886         this.endDrag(e);
90887     },
90888
90889     /**
90890      * @private
90891      * Stop the drag operation, and remove active mouse listeners.
90892      */
90893     endDrag: function(e) {
90894         var doc = Ext.getDoc(),
90895         wasActive = this.active;
90896
90897         doc.un('mousemove', this.onMouseMove, this);
90898         doc.un('mouseup', this.onMouseUp, this);
90899         doc.un('selectstart', this.stopSelect, this);
90900         this.clearStart();
90901         this.active = false;
90902         if (wasActive) {
90903             this.onEnd(e);
90904             this.fireEvent('dragend', this, e);
90905         }
90906         // Private property calculated when first required and only cached during a drag
90907         delete this._constrainRegion;
90908
90909         // Remove flag from event singleton.  Using "Ext.EventObject" here since "endDrag" is called directly in some cases without an "e" param
90910         delete Ext.EventObject.dragTracked;
90911     },
90912
90913     triggerStart: function(e) {
90914         this.clearStart();
90915         this.active = true;
90916         this.onStart(e);
90917         this.fireEvent('dragstart', this, e);
90918     },
90919
90920     clearStart : function() {
90921         if (this.timer) {
90922             clearTimeout(this.timer);
90923             delete this.timer;
90924         }
90925     },
90926
90927     stopSelect : function(e) {
90928         e.stopEvent();
90929         return false;
90930     },
90931
90932     /**
90933      * Template method which should be overridden by each DragTracker instance. Called when the user first clicks and
90934      * holds the mouse button down. Return false to disallow the drag
90935      * @param {Ext.EventObject} e The event object
90936      * @template
90937      */
90938     onBeforeStart : function(e) {
90939
90940     },
90941
90942     /**
90943      * Template method which should be overridden by each DragTracker instance. Called when a drag operation starts
90944      * (e.g. the user has moved the tracked element beyond the specified tolerance)
90945      * @param {Ext.EventObject} e The event object
90946      * @template
90947      */
90948     onStart : function(xy) {
90949
90950     },
90951
90952     /**
90953      * Template method which should be overridden by each DragTracker instance. Called whenever a drag has been detected.
90954      * @param {Ext.EventObject} e The event object
90955      * @template
90956      */
90957     onDrag : function(e) {
90958
90959     },
90960
90961     /**
90962      * Template method which should be overridden by each DragTracker instance. Called when a drag operation has been completed
90963      * (e.g. the user clicked and held the mouse down, dragged the element and then released the mouse button)
90964      * @param {Ext.EventObject} e The event object
90965      * @template
90966      */
90967     onEnd : function(e) {
90968
90969     },
90970
90971     /**
90972      * </p>Returns the drag target. This is usually the DragTracker's encapsulating element.</p>
90973      * <p>If the {@link #delegate} option is being used, this may be a child element which matches the
90974      * {@link #delegate} selector.</p>
90975      * @return {Ext.Element} The element currently being tracked.
90976      */
90977     getDragTarget : function(){
90978         return this.dragTarget;
90979     },
90980
90981     /**
90982      * @private
90983      * @returns {Ext.Element} The DragTracker's encapsulating element.
90984      */
90985     getDragCt : function(){
90986         return this.el;
90987     },
90988
90989     /**
90990      * @private
90991      * Return the Region into which the drag operation is constrained.
90992      * Either the XY pointer itself can be constrained, or the dragTarget element
90993      * The private property _constrainRegion is cached until onMouseUp
90994      */
90995     getConstrainRegion: function() {
90996         if (this.constrainTo) {
90997             if (this.constrainTo instanceof Ext.util.Region) {
90998                 return this.constrainTo;
90999             }
91000             if (!this._constrainRegion) {
91001                 this._constrainRegion = Ext.fly(this.constrainTo).getViewRegion();
91002             }
91003         } else {
91004             if (!this._constrainRegion) {
91005                 this._constrainRegion = this.getDragCt().getViewRegion();
91006             }
91007         }
91008         return this._constrainRegion;
91009     },
91010
91011     getXY : function(constrain){
91012         return constrain ? this.constrainModes[constrain](this, this.lastXY) : this.lastXY;
91013     },
91014
91015     /**
91016      * Returns the X, Y offset of the current mouse position from the mousedown point.
91017      *
91018      * This method may optionally constrain the real offset values, and returns a point coerced in one
91019      * of two modes:
91020      *
91021      *  - `point`
91022      *    The current mouse position is coerced into the constrainRegion and the resulting position is returned.
91023      *  - `dragTarget`
91024      *    The new {@link Ext.util.Region Region} of the {@link #getDragTarget dragTarget} is calculated
91025      *    based upon the current mouse position, and then coerced into the constrainRegion. The returned
91026      *    mouse position is then adjusted by the same delta as was used to coerce the region.\
91027      *
91028      * @param constrainMode {String} (Optional) If omitted the true mouse position is returned. May be passed
91029      * as `point` or `dragTarget`. See above.
91030      * @returns {Number[]} The `X, Y` offset from the mousedown point, optionally constrained.
91031      */
91032     getOffset : function(constrain){
91033         var xy = this.getXY(constrain),
91034             s = this.startXY;
91035
91036         return [xy[0]-s[0], xy[1]-s[1]];
91037     },
91038
91039     constrainModes: {
91040         // Constrain the passed point to within the constrain region
91041         point: function(me, xy) {
91042             var dr = me.dragRegion,
91043                 constrainTo = me.getConstrainRegion();
91044
91045             // No constraint
91046             if (!constrainTo) {
91047                 return xy;
91048             }
91049
91050             dr.x = dr.left = dr[0] = dr.right = xy[0];
91051             dr.y = dr.top = dr[1] = dr.bottom = xy[1];
91052             dr.constrainTo(constrainTo);
91053
91054             return [dr.left, dr.top];
91055         },
91056
91057         // Constrain the dragTarget to within the constrain region. Return the passed xy adjusted by the same delta.
91058         dragTarget: function(me, xy) {
91059             var s = me.startXY,
91060                 dr = me.startRegion.copy(),
91061                 constrainTo = me.getConstrainRegion(),
91062                 adjust;
91063
91064             // No constraint
91065             if (!constrainTo) {
91066                 return xy;
91067             }
91068
91069             // See where the passed XY would put the dragTarget if translated by the unconstrained offset.
91070             // If it overflows, we constrain the passed XY to bring the potential
91071             // region back within the boundary.
91072             dr.translateBy(xy[0]-s[0], xy[1]-s[1]);
91073
91074             // Constrain the X coordinate by however much the dragTarget overflows
91075             if (dr.right > constrainTo.right) {
91076                 xy[0] += adjust = (constrainTo.right - dr.right);    // overflowed the right
91077                 dr.left += adjust;
91078             }
91079             if (dr.left < constrainTo.left) {
91080                 xy[0] += (constrainTo.left - dr.left);      // overflowed the left
91081             }
91082
91083             // Constrain the Y coordinate by however much the dragTarget overflows
91084             if (dr.bottom > constrainTo.bottom) {
91085                 xy[1] += adjust = (constrainTo.bottom - dr.bottom);  // overflowed the bottom
91086                 dr.top += adjust;
91087             }
91088             if (dr.top < constrainTo.top) {
91089                 xy[1] += (constrainTo.top - dr.top);        // overflowed the top
91090             }
91091             return xy;
91092         }
91093     }
91094 });
91095 /**
91096  * @class Ext.dd.DragZone
91097  * @extends Ext.dd.DragSource
91098  * <p>This class provides a container DD instance that allows dragging of multiple child source nodes.</p>
91099  * <p>This class does not move the drag target nodes, but a proxy element which may contain
91100  * any DOM structure you wish. The DOM element to show in the proxy is provided by either a
91101  * provided implementation of {@link #getDragData}, or by registered draggables registered with {@link Ext.dd.Registry}</p>
91102  * <p>If you wish to provide draggability for an arbitrary number of DOM nodes, each of which represent some
91103  * application object (For example nodes in a {@link Ext.view.View DataView}) then use of this class
91104  * is the most efficient way to "activate" those nodes.</p>
91105  * <p>By default, this class requires that draggable child nodes are registered with {@link Ext.dd.Registry}.
91106  * However a simpler way to allow a DragZone to manage any number of draggable elements is to configure
91107  * the DragZone with  an implementation of the {@link #getDragData} method which interrogates the passed
91108  * mouse event to see if it has taken place within an element, or class of elements. This is easily done
91109  * by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
91110  * {@link Ext.DomQuery} selector. For example, to make the nodes of a DataView draggable, use the following
91111  * technique. Knowledge of the use of the DataView is required:</p><pre><code>
91112 myDataView.on('render', function(v) {
91113     myDataView.dragZone = new Ext.dd.DragZone(v.getEl(), {
91114
91115 //      On receipt of a mousedown event, see if it is within a DataView node.
91116 //      Return a drag data object if so.
91117         getDragData: function(e) {
91118
91119 //          Use the DataView's own itemSelector (a mandatory property) to
91120 //          test if the mousedown is within one of the DataView's nodes.
91121             var sourceEl = e.getTarget(v.itemSelector, 10);
91122
91123 //          If the mousedown is within a DataView node, clone the node to produce
91124 //          a ddel element for use by the drag proxy. Also add application data
91125 //          to the returned data object.
91126             if (sourceEl) {
91127                 d = sourceEl.cloneNode(true);
91128                 d.id = Ext.id();
91129                 return {
91130                     ddel: d,
91131                     sourceEl: sourceEl,
91132                     repairXY: Ext.fly(sourceEl).getXY(),
91133                     sourceStore: v.store,
91134                     draggedRecord: v.{@link Ext.view.View#getRecord getRecord}(sourceEl)
91135                 }
91136             }
91137         },
91138
91139 //      Provide coordinates for the proxy to slide back to on failed drag.
91140 //      This is the original XY coordinates of the draggable element captured
91141 //      in the getDragData method.
91142         getRepairXY: function() {
91143             return this.dragData.repairXY;
91144         }
91145     });
91146 });</code></pre>
91147  * See the {@link Ext.dd.DropZone DropZone} documentation for details about building a DropZone which
91148  * cooperates with this DragZone.
91149  */
91150 Ext.define('Ext.dd.DragZone', {
91151
91152     extend: 'Ext.dd.DragSource',
91153
91154     /**
91155      * Creates new DragZone.
91156      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
91157      * @param {Object} config
91158      */
91159     constructor : function(el, config){
91160         this.callParent([el, config]);
91161         if (this.containerScroll) {
91162             Ext.dd.ScrollManager.register(this.el);
91163         }
91164     },
91165
91166     /**
91167      * This property contains the data representing the dragged object. This data is set up by the implementation
91168      * of the {@link #getDragData} method. It must contain a <tt>ddel</tt> property, but can contain
91169      * any other data according to the application's needs.
91170      * @type Object
91171      * @property dragData
91172      */
91173
91174     /**
91175      * @cfg {Boolean} containerScroll True to register this container with the Scrollmanager
91176      * for auto scrolling during drag operations.
91177      */
91178
91179     /**
91180      * Called when a mousedown occurs in this container. Looks in {@link Ext.dd.Registry}
91181      * for a valid target to drag based on the mouse down. Override this method
91182      * to provide your own lookup logic (e.g. finding a child by class name). Make sure your returned
91183      * object has a "ddel" attribute (with an HTML Element) for other functions to work.
91184      * @param {Event} e The mouse down event
91185      * @return {Object} The dragData
91186      */
91187     getDragData : function(e){
91188         return Ext.dd.Registry.getHandleFromEvent(e);
91189     },
91190
91191     /**
91192      * Called once drag threshold has been reached to initialize the proxy element. By default, it clones the
91193      * this.dragData.ddel
91194      * @param {Number} x The x position of the click on the dragged object
91195      * @param {Number} y The y position of the click on the dragged object
91196      * @return {Boolean} true to continue the drag, false to cancel
91197      */
91198     onInitDrag : function(x, y){
91199         this.proxy.update(this.dragData.ddel.cloneNode(true));
91200         this.onStartDrag(x, y);
91201         return true;
91202     },
91203
91204     /**
91205      * Called after a repair of an invalid drop. By default, highlights this.dragData.ddel
91206      */
91207     afterRepair : function(){
91208         var me = this;
91209         if (Ext.enableFx) {
91210             Ext.fly(me.dragData.ddel).highlight(me.repairHighlightColor);
91211         }
91212         me.dragging = false;
91213     },
91214
91215     /**
91216      * Called before a repair of an invalid drop to get the XY to animate to. By default returns
91217      * the XY of this.dragData.ddel
91218      * @param {Event} e The mouse up event
91219      * @return {Number[]} The xy location (e.g. [100, 200])
91220      */
91221     getRepairXY : function(e){
91222         return Ext.Element.fly(this.dragData.ddel).getXY();
91223     },
91224
91225     destroy : function(){
91226         this.callParent();
91227         if (this.containerScroll) {
91228             Ext.dd.ScrollManager.unregister(this.el);
91229         }
91230     }
91231 });
91232
91233 /**
91234  * @class Ext.dd.ScrollManager
91235  * <p>Provides automatic scrolling of overflow regions in the page during drag operations.</p>
91236  * <p>The ScrollManager configs will be used as the defaults for any scroll container registered with it,
91237  * but you can also override most of the configs per scroll container by adding a
91238  * <tt>ddScrollConfig</tt> object to the target element that contains these properties: {@link #hthresh},
91239  * {@link #vthresh}, {@link #increment} and {@link #frequency}.  Example usage:
91240  * <pre><code>
91241 var el = Ext.get('scroll-ct');
91242 el.ddScrollConfig = {
91243     vthresh: 50,
91244     hthresh: -1,
91245     frequency: 100,
91246     increment: 200
91247 };
91248 Ext.dd.ScrollManager.register(el);
91249 </code></pre>
91250  * Note: This class is designed to be used in "Point Mode
91251  * @singleton
91252  */
91253 Ext.define('Ext.dd.ScrollManager', {
91254     singleton: true,
91255     requires: [
91256         'Ext.dd.DragDropManager'
91257     ],
91258
91259     constructor: function() {
91260         var ddm = Ext.dd.DragDropManager;
91261         ddm.fireEvents = Ext.Function.createSequence(ddm.fireEvents, this.onFire, this);
91262         ddm.stopDrag = Ext.Function.createSequence(ddm.stopDrag, this.onStop, this);
91263         this.doScroll = Ext.Function.bind(this.doScroll, this);
91264         this.ddmInstance = ddm;
91265         this.els = {};
91266         this.dragEl = null;
91267         this.proc = {};
91268     },
91269
91270     onStop: function(e){
91271         var sm = Ext.dd.ScrollManager;
91272         sm.dragEl = null;
91273         sm.clearProc();
91274     },
91275
91276     triggerRefresh: function() {
91277         if (this.ddmInstance.dragCurrent) {
91278             this.ddmInstance.refreshCache(this.ddmInstance.dragCurrent.groups);
91279         }
91280     },
91281
91282     doScroll: function() {
91283         if (this.ddmInstance.dragCurrent) {
91284             var proc   = this.proc,
91285                 procEl = proc.el,
91286                 ddScrollConfig = proc.el.ddScrollConfig,
91287                 inc = ddScrollConfig ? ddScrollConfig.increment : this.increment;
91288
91289             if (!this.animate) {
91290                 if (procEl.scroll(proc.dir, inc)) {
91291                     this.triggerRefresh();
91292                 }
91293             } else {
91294                 procEl.scroll(proc.dir, inc, true, this.animDuration, this.triggerRefresh);
91295             }
91296         }
91297     },
91298
91299     clearProc: function() {
91300         var proc = this.proc;
91301         if (proc.id) {
91302             clearInterval(proc.id);
91303         }
91304         proc.id = 0;
91305         proc.el = null;
91306         proc.dir = "";
91307     },
91308
91309     startProc: function(el, dir) {
91310         this.clearProc();
91311         this.proc.el = el;
91312         this.proc.dir = dir;
91313         var group = el.ddScrollConfig ? el.ddScrollConfig.ddGroup : undefined,
91314             freq  = (el.ddScrollConfig && el.ddScrollConfig.frequency)
91315                   ? el.ddScrollConfig.frequency
91316                   : this.frequency;
91317
91318         if (group === undefined || this.ddmInstance.dragCurrent.ddGroup == group) {
91319             this.proc.id = setInterval(this.doScroll, freq);
91320         }
91321     },
91322
91323     onFire: function(e, isDrop) {
91324         if (isDrop || !this.ddmInstance.dragCurrent) {
91325             return;
91326         }
91327         if (!this.dragEl || this.dragEl != this.ddmInstance.dragCurrent) {
91328             this.dragEl = this.ddmInstance.dragCurrent;
91329             // refresh regions on drag start
91330             this.refreshCache();
91331         }
91332
91333         var xy = e.getXY(),
91334             pt = e.getPoint(),
91335             proc = this.proc,
91336             els = this.els;
91337
91338         for (var id in els) {
91339             var el = els[id], r = el._region;
91340             var c = el.ddScrollConfig ? el.ddScrollConfig : this;
91341             if (r && r.contains(pt) && el.isScrollable()) {
91342                 if (r.bottom - pt.y <= c.vthresh) {
91343                     if(proc.el != el){
91344                         this.startProc(el, "down");
91345                     }
91346                     return;
91347                 }else if (r.right - pt.x <= c.hthresh) {
91348                     if (proc.el != el) {
91349                         this.startProc(el, "left");
91350                     }
91351                     return;
91352                 } else if(pt.y - r.top <= c.vthresh) {
91353                     if (proc.el != el) {
91354                         this.startProc(el, "up");
91355                     }
91356                     return;
91357                 } else if(pt.x - r.left <= c.hthresh) {
91358                     if (proc.el != el) {
91359                         this.startProc(el, "right");
91360                     }
91361                     return;
91362                 }
91363             }
91364         }
91365         this.clearProc();
91366     },
91367
91368     /**
91369      * Registers new overflow element(s) to auto scroll
91370      * @param {String/HTMLElement/Ext.Element/String[]/HTMLElement[]/Ext.Element[]} el
91371      * The id of or the element to be scrolled or an array of either
91372      */
91373     register : function(el){
91374         if (Ext.isArray(el)) {
91375             for(var i = 0, len = el.length; i < len; i++) {
91376                     this.register(el[i]);
91377             }
91378         } else {
91379             el = Ext.get(el);
91380             this.els[el.id] = el;
91381         }
91382     },
91383
91384     /**
91385      * Unregisters overflow element(s) so they are no longer scrolled
91386      * @param {String/HTMLElement/Ext.Element/String[]/HTMLElement[]/Ext.Element[]} el
91387      * The id of or the element to be removed or an array of either
91388      */
91389     unregister : function(el){
91390         if(Ext.isArray(el)){
91391             for (var i = 0, len = el.length; i < len; i++) {
91392                 this.unregister(el[i]);
91393             }
91394         }else{
91395             el = Ext.get(el);
91396             delete this.els[el.id];
91397         }
91398     },
91399
91400     /**
91401      * The number of pixels from the top or bottom edge of a container the pointer needs to be to
91402      * trigger scrolling
91403      * @type Number
91404      */
91405     vthresh : 25,
91406     /**
91407      * The number of pixels from the right or left edge of a container the pointer needs to be to
91408      * trigger scrolling
91409      * @type Number
91410      */
91411     hthresh : 25,
91412
91413     /**
91414      * The number of pixels to scroll in each scroll increment
91415      * @type Number
91416      */
91417     increment : 100,
91418
91419     /**
91420      * The frequency of scrolls in milliseconds
91421      * @type Number
91422      */
91423     frequency : 500,
91424
91425     /**
91426      * True to animate the scroll
91427      * @type Boolean
91428      */
91429     animate: true,
91430
91431     /**
91432      * The animation duration in seconds - MUST BE less than Ext.dd.ScrollManager.frequency!
91433      * @type Number
91434      */
91435     animDuration: 0.4,
91436
91437     /**
91438      * The named drag drop {@link Ext.dd.DragSource#ddGroup group} to which this container belongs.
91439      * If a ddGroup is specified, then container scrolling will only occur when a dragged object is in the same ddGroup.
91440      * @type String
91441      */
91442     ddGroup: undefined,
91443
91444     /**
91445      * Manually trigger a cache refresh.
91446      */
91447     refreshCache : function(){
91448         var els = this.els,
91449             id;
91450         for (id in els) {
91451             if(typeof els[id] == 'object'){ // for people extending the object prototype
91452                 els[id]._region = els[id].getRegion();
91453             }
91454         }
91455     }
91456 });
91457
91458 /**
91459  * @class Ext.dd.DropTarget
91460  * @extends Ext.dd.DDTarget
91461  * A simple class that provides the basic implementation needed to make any element a drop target that can have
91462  * draggable items dropped onto it.  The drop has no effect until an implementation of notifyDrop is provided.
91463  */
91464 Ext.define('Ext.dd.DropTarget', {
91465     extend: 'Ext.dd.DDTarget',
91466     requires: ['Ext.dd.ScrollManager'],
91467
91468     /**
91469      * Creates new DropTarget.
91470      * @param {String/HTMLElement/Ext.Element} el The container element or ID of it.
91471      * @param {Object} config
91472      */
91473     constructor : function(el, config){
91474         this.el = Ext.get(el);
91475
91476         Ext.apply(this, config);
91477
91478         if(this.containerScroll){
91479             Ext.dd.ScrollManager.register(this.el);
91480         }
91481
91482         this.callParent([this.el.dom, this.ddGroup || this.group,
91483               {isTarget: true}]);
91484     },
91485
91486     /**
91487      * @cfg {String} ddGroup
91488      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
91489      * interact with other drag drop objects in the same group.
91490      */
91491     /**
91492      * @cfg {String} [overClass=""]
91493      * The CSS class applied to the drop target element while the drag source is over it.
91494      */
91495     /**
91496      * @cfg {String} [dropAllowed="x-dd-drop-ok"]
91497      * The CSS class returned to the drag source when drop is allowed.
91498      */
91499     dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
91500     /**
91501      * @cfg {String} [dropNotAllowed="x-dd-drop-nodrop"]
91502      * The CSS class returned to the drag source when drop is not allowed.
91503      */
91504     dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
91505
91506     // private
91507     isTarget : true,
91508
91509     // private
91510     isNotifyTarget : true,
91511
91512     /**
91513      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source is now over the
91514      * target.  This default implementation adds the CSS class specified by overClass (if any) to the drop element
91515      * and returns the dropAllowed config value.  This method should be overridden if drop validation is required.
91516      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91517      * @param {Event} e The event
91518      * @param {Object} data An object containing arbitrary data supplied by the drag source
91519      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91520      * underlying {@link Ext.dd.StatusProxy} can be updated
91521      */
91522     notifyEnter : function(dd, e, data){
91523         if(this.overClass){
91524             this.el.addCls(this.overClass);
91525         }
91526         return this.dropAllowed;
91527     },
91528
91529     /**
91530      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the target.
91531      * This method will be called on every mouse movement while the drag source is over the drop target.
91532      * This default implementation simply returns the dropAllowed config value.
91533      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91534      * @param {Event} e The event
91535      * @param {Object} data An object containing arbitrary data supplied by the drag source
91536      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91537      * underlying {@link Ext.dd.StatusProxy} can be updated
91538      */
91539     notifyOver : function(dd, e, data){
91540         return this.dropAllowed;
91541     },
91542
91543     /**
91544      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source has been dragged
91545      * out of the target without dropping.  This default implementation simply removes the CSS class specified by
91546      * overClass (if any) from the drop element.
91547      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91548      * @param {Event} e The event
91549      * @param {Object} data An object containing arbitrary data supplied by the drag source
91550      */
91551     notifyOut : function(dd, e, data){
91552         if(this.overClass){
91553             this.el.removeCls(this.overClass);
91554         }
91555     },
91556
91557     /**
91558      * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the dragged item has
91559      * been dropped on it.  This method has no default implementation and returns false, so you must provide an
91560      * implementation that does something to process the drop event and returns true so that the drag source's
91561      * repair action does not run.
91562      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91563      * @param {Event} e The event
91564      * @param {Object} data An object containing arbitrary data supplied by the drag source
91565      * @return {Boolean} False if the drop was invalid.
91566      */
91567     notifyDrop : function(dd, e, data){
91568         return false;
91569     },
91570
91571     destroy : function(){
91572         this.callParent();
91573         if(this.containerScroll){
91574             Ext.dd.ScrollManager.unregister(this.el);
91575         }
91576     }
91577 });
91578
91579 /**
91580  * @class Ext.dd.Registry
91581  * Provides easy access to all drag drop components that are registered on a page.  Items can be retrieved either
91582  * directly by DOM node id, or by passing in the drag drop event that occurred and looking up the event target.
91583  * @singleton
91584  */
91585 Ext.define('Ext.dd.Registry', {
91586     singleton: true,
91587     constructor: function() {
91588         this.elements = {}; 
91589         this.handles = {}; 
91590         this.autoIdSeed = 0;
91591     },
91592     
91593     getId: function(el, autogen){
91594         if(typeof el == "string"){
91595             return el;
91596         }
91597         var id = el.id;
91598         if(!id && autogen !== false){
91599             id = "extdd-" + (++this.autoIdSeed);
91600             el.id = id;
91601         }
91602         return id;
91603     },
91604     
91605     /**
91606      * Resgister a drag drop element
91607      * @param {String/HTMLElement} element The id or DOM node to register
91608      * @param {Object} data (optional) An custom data object that will be passed between the elements that are involved
91609      * in drag drop operations.  You can populate this object with any arbitrary properties that your own code
91610      * knows how to interpret, plus there are some specific properties known to the Registry that should be
91611      * populated in the data object (if applicable):
91612      * <pre>
91613 Value      Description<br />
91614 ---------  ------------------------------------------<br />
91615 handles    Array of DOM nodes that trigger dragging<br />
91616            for the element being registered<br />
91617 isHandle   True if the element passed in triggers<br />
91618            dragging itself, else false
91619 </pre>
91620      */
91621     register : function(el, data){
91622         data = data || {};
91623         if (typeof el == "string") {
91624             el = document.getElementById(el);
91625         }
91626         data.ddel = el;
91627         this.elements[this.getId(el)] = data;
91628         if (data.isHandle !== false) {
91629             this.handles[data.ddel.id] = data;
91630         }
91631         if (data.handles) {
91632             var hs = data.handles;
91633             for (var i = 0, len = hs.length; i < len; i++) {
91634                 this.handles[this.getId(hs[i])] = data;
91635             }
91636         }
91637     },
91638
91639     /**
91640      * Unregister a drag drop element
91641      * @param {String/HTMLElement} element The id or DOM node to unregister
91642      */
91643     unregister : function(el){
91644         var id = this.getId(el, false);
91645         var data = this.elements[id];
91646         if(data){
91647             delete this.elements[id];
91648             if(data.handles){
91649                 var hs = data.handles;
91650                 for (var i = 0, len = hs.length; i < len; i++) {
91651                     delete this.handles[this.getId(hs[i], false)];
91652                 }
91653             }
91654         }
91655     },
91656
91657     /**
91658      * Returns the handle registered for a DOM Node by id
91659      * @param {String/HTMLElement} id The DOM node or id to look up
91660      * @return {Object} handle The custom handle data
91661      */
91662     getHandle : function(id){
91663         if(typeof id != "string"){ // must be element?
91664             id = id.id;
91665         }
91666         return this.handles[id];
91667     },
91668
91669     /**
91670      * Returns the handle that is registered for the DOM node that is the target of the event
91671      * @param {Event} e The event
91672      * @return {Object} handle The custom handle data
91673      */
91674     getHandleFromEvent : function(e){
91675         var t = e.getTarget();
91676         return t ? this.handles[t.id] : null;
91677     },
91678
91679     /**
91680      * Returns a custom data object that is registered for a DOM node by id
91681      * @param {String/HTMLElement} id The DOM node or id to look up
91682      * @return {Object} data The custom data
91683      */
91684     getTarget : function(id){
91685         if(typeof id != "string"){ // must be element?
91686             id = id.id;
91687         }
91688         return this.elements[id];
91689     },
91690
91691     /**
91692      * Returns a custom data object that is registered for the DOM node that is the target of the event
91693      * @param {Event} e The event
91694      * @return {Object} data The custom data
91695      */
91696     getTargetFromEvent : function(e){
91697         var t = e.getTarget();
91698         return t ? this.elements[t.id] || this.handles[t.id] : null;
91699     }
91700 });
91701 /**
91702  * @class Ext.dd.DropZone
91703  * @extends Ext.dd.DropTarget
91704
91705 This class provides a container DD instance that allows dropping on multiple child target nodes.
91706
91707 By default, this class requires that child nodes accepting drop are registered with {@link Ext.dd.Registry}.
91708 However a simpler way to allow a DropZone to manage any number of target elements is to configure the
91709 DropZone with an implementation of {@link #getTargetFromEvent} which interrogates the passed
91710 mouse event to see if it has taken place within an element, or class of elements. This is easily done
91711 by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
91712 {@link Ext.DomQuery} selector.
91713
91714 Once the DropZone has detected through calling getTargetFromEvent, that the mouse is over
91715 a drop target, that target is passed as the first parameter to {@link #onNodeEnter}, {@link #onNodeOver},
91716 {@link #onNodeOut}, {@link #onNodeDrop}. You may configure the instance of DropZone with implementations
91717 of these methods to provide application-specific behaviour for these events to update both
91718 application state, and UI state.
91719
91720 For example to make a GridPanel a cooperating target with the example illustrated in
91721 {@link Ext.dd.DragZone DragZone}, the following technique might be used:
91722
91723     myGridPanel.on('render', function() {
91724         myGridPanel.dropZone = new Ext.dd.DropZone(myGridPanel.getView().scroller, {
91725
91726             // If the mouse is over a grid row, return that node. This is
91727             // provided as the "target" parameter in all "onNodeXXXX" node event handling functions
91728             getTargetFromEvent: function(e) {
91729                 return e.getTarget(myGridPanel.getView().rowSelector);
91730             },
91731
91732             // On entry into a target node, highlight that node.
91733             onNodeEnter : function(target, dd, e, data){ 
91734                 Ext.fly(target).addCls('my-row-highlight-class');
91735             },
91736
91737             // On exit from a target node, unhighlight that node.
91738             onNodeOut : function(target, dd, e, data){ 
91739                 Ext.fly(target).removeCls('my-row-highlight-class');
91740             },
91741
91742             // While over a target node, return the default drop allowed class which
91743             // places a "tick" icon into the drag proxy.
91744             onNodeOver : function(target, dd, e, data){ 
91745                 return Ext.dd.DropZone.prototype.dropAllowed;
91746             },
91747
91748             // On node drop we can interrogate the target to find the underlying
91749             // application object that is the real target of the dragged data.
91750             // In this case, it is a Record in the GridPanel's Store.
91751             // We can use the data set up by the DragZone's getDragData method to read
91752             // any data we decided to attach in the DragZone's getDragData method.
91753             onNodeDrop : function(target, dd, e, data){
91754                 var rowIndex = myGridPanel.getView().findRowIndex(target);
91755                 var r = myGridPanel.getStore().getAt(rowIndex);
91756                 Ext.Msg.alert('Drop gesture', 'Dropped Record id ' + data.draggedRecord.id +
91757                     ' on Record id ' + r.id);
91758                 return true;
91759             }
91760         });
91761     }
91762
91763 See the {@link Ext.dd.DragZone DragZone} documentation for details about building a DragZone which
91764 cooperates with this DropZone.
91765
91766  * @markdown
91767  */
91768 Ext.define('Ext.dd.DropZone', {
91769     extend: 'Ext.dd.DropTarget',
91770     requires: ['Ext.dd.Registry'],
91771
91772     /**
91773      * Returns a custom data object associated with the DOM node that is the target of the event.  By default
91774      * this looks up the event target in the {@link Ext.dd.Registry}, although you can override this method to
91775      * provide your own custom lookup.
91776      * @param {Event} e The event
91777      * @return {Object} data The custom data
91778      */
91779     getTargetFromEvent : function(e){
91780         return Ext.dd.Registry.getTargetFromEvent(e);
91781     },
91782
91783     /**
91784      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has entered a drop node
91785      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
91786      * This method has no default implementation and should be overridden to provide
91787      * node-specific processing if necessary.
91788      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from 
91789      * {@link #getTargetFromEvent} for this node)
91790      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91791      * @param {Event} e The event
91792      * @param {Object} data An object containing arbitrary data supplied by the drag source
91793      */
91794     onNodeEnter : function(n, dd, e, data){
91795         
91796     },
91797
91798     /**
91799      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node
91800      * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
91801      * The default implementation returns this.dropNotAllowed, so it should be
91802      * overridden to provide the proper feedback.
91803      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
91804      * {@link #getTargetFromEvent} for this node)
91805      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91806      * @param {Event} e The event
91807      * @param {Object} data An object containing arbitrary data supplied by the drag source
91808      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91809      * underlying {@link Ext.dd.StatusProxy} can be updated
91810      */
91811     onNodeOver : function(n, dd, e, data){
91812         return this.dropAllowed;
91813     },
91814
91815     /**
91816      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dragged out of
91817      * the drop node without dropping.  This method has no default implementation and should be overridden to provide
91818      * node-specific processing if necessary.
91819      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
91820      * {@link #getTargetFromEvent} for this node)
91821      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91822      * @param {Event} e The event
91823      * @param {Object} data An object containing arbitrary data supplied by the drag source
91824      */
91825     onNodeOut : function(n, dd, e, data){
91826         
91827     },
91828
91829     /**
91830      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped onto
91831      * the drop node.  The default implementation returns false, so it should be overridden to provide the
91832      * appropriate processing of the drop event and return true so that the drag source's repair action does not run.
91833      * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
91834      * {@link #getTargetFromEvent} for this node)
91835      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91836      * @param {Event} e The event
91837      * @param {Object} data An object containing arbitrary data supplied by the drag source
91838      * @return {Boolean} True if the drop was valid, else false
91839      */
91840     onNodeDrop : function(n, dd, e, data){
91841         return false;
91842     },
91843
91844     /**
91845      * Called while the DropZone determines that a {@link Ext.dd.DragSource} is being dragged over it,
91846      * but not over any of its registered drop nodes.  The default implementation returns this.dropNotAllowed, so
91847      * it should be overridden to provide the proper feedback if necessary.
91848      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91849      * @param {Event} e The event
91850      * @param {Object} data An object containing arbitrary data supplied by the drag source
91851      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91852      * underlying {@link Ext.dd.StatusProxy} can be updated
91853      */
91854     onContainerOver : function(dd, e, data){
91855         return this.dropNotAllowed;
91856     },
91857
91858     /**
91859      * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped on it,
91860      * but not on any of its registered drop nodes.  The default implementation returns false, so it should be
91861      * overridden to provide the appropriate processing of the drop event if you need the drop zone itself to
91862      * be able to accept drops.  It should return true when valid so that the drag source's repair action does not run.
91863      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91864      * @param {Event} e The event
91865      * @param {Object} data An object containing arbitrary data supplied by the drag source
91866      * @return {Boolean} True if the drop was valid, else false
91867      */
91868     onContainerDrop : function(dd, e, data){
91869         return false;
91870     },
91871
91872     /**
91873      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source is now over
91874      * the zone.  The default implementation returns this.dropNotAllowed and expects that only registered drop
91875      * nodes can process drag drop operations, so if you need the drop zone itself to be able to process drops
91876      * you should override this method and provide a custom implementation.
91877      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91878      * @param {Event} e The event
91879      * @param {Object} data An object containing arbitrary data supplied by the drag source
91880      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91881      * underlying {@link Ext.dd.StatusProxy} can be updated
91882      */
91883     notifyEnter : function(dd, e, data){
91884         return this.dropNotAllowed;
91885     },
91886
91887     /**
91888      * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the drop zone.
91889      * This method will be called on every mouse movement while the drag source is over the drop zone.
91890      * It will call {@link #onNodeOver} while the drag source is over a registered node, and will also automatically
91891      * delegate to the appropriate node-specific methods as necessary when the drag source enters and exits
91892      * registered nodes ({@link #onNodeEnter}, {@link #onNodeOut}). If the drag source is not currently over a
91893      * registered node, it will call {@link #onContainerOver}.
91894      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91895      * @param {Event} e The event
91896      * @param {Object} data An object containing arbitrary data supplied by the drag source
91897      * @return {String} status The CSS class that communicates the drop status back to the source so that the
91898      * underlying {@link Ext.dd.StatusProxy} can be updated
91899      */
91900     notifyOver : function(dd, e, data){
91901         var n = this.getTargetFromEvent(e);
91902         if(!n) { // not over valid drop target
91903             if(this.lastOverNode){
91904                 this.onNodeOut(this.lastOverNode, dd, e, data);
91905                 this.lastOverNode = null;
91906             }
91907             return this.onContainerOver(dd, e, data);
91908         }
91909         if(this.lastOverNode != n){
91910             if(this.lastOverNode){
91911                 this.onNodeOut(this.lastOverNode, dd, e, data);
91912             }
91913             this.onNodeEnter(n, dd, e, data);
91914             this.lastOverNode = n;
91915         }
91916         return this.onNodeOver(n, dd, e, data);
91917     },
91918
91919     /**
91920      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source has been dragged
91921      * out of the zone without dropping.  If the drag source is currently over a registered node, the notification
91922      * will be delegated to {@link #onNodeOut} for node-specific handling, otherwise it will be ignored.
91923      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
91924      * @param {Event} e The event
91925      * @param {Object} data An object containing arbitrary data supplied by the drag zone
91926      */
91927     notifyOut : function(dd, e, data){
91928         if(this.lastOverNode){
91929             this.onNodeOut(this.lastOverNode, dd, e, data);
91930             this.lastOverNode = null;
91931         }
91932     },
91933
91934     /**
91935      * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the dragged item has
91936      * been dropped on it.  The drag zone will look up the target node based on the event passed in, and if there
91937      * is a node registered for that event, it will delegate to {@link #onNodeDrop} for node-specific handling,
91938      * otherwise it will call {@link #onContainerDrop}.
91939      * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
91940      * @param {Event} e The event
91941      * @param {Object} data An object containing arbitrary data supplied by the drag source
91942      * @return {Boolean} False if the drop was invalid.
91943      */
91944     notifyDrop : function(dd, e, data){
91945         if(this.lastOverNode){
91946             this.onNodeOut(this.lastOverNode, dd, e, data);
91947             this.lastOverNode = null;
91948         }
91949         var n = this.getTargetFromEvent(e);
91950         return n ?
91951             this.onNodeDrop(n, dd, e, data) :
91952             this.onContainerDrop(dd, e, data);
91953     },
91954
91955     // private
91956     triggerCacheRefresh : function() {
91957         Ext.dd.DDM.refreshCache(this.groups);
91958     }
91959 });
91960 /**
91961  * @class Ext.flash.Component
91962  * @extends Ext.Component
91963  *
91964  * A simple Component for displaying an Adobe Flash SWF movie. The movie will be sized and can participate
91965  * in layout like any other Component.
91966  *
91967  * This component requires the third-party SWFObject library version 2.2 or above. It is not included within
91968  * the ExtJS distribution, so you will have to include it into your page manually in order to use this component.
91969  * The SWFObject library can be downloaded from the [SWFObject project page](http://code.google.com/p/swfobject)
91970  * and then simply import it into the head of your HTML document:
91971  *
91972  *     <script type="text/javascript" src="path/to/local/swfobject.js"></script>
91973  *
91974  * ## Configuration
91975  *
91976  * This component allows several options for configuring how the target Flash movie is embedded. The most
91977  * important is the required {@link #url} which points to the location of the Flash movie to load. Other
91978  * configurations include:
91979  *
91980  * - {@link #backgroundColor}
91981  * - {@link #wmode}
91982  * - {@link #flashVars}
91983  * - {@link #flashParams}
91984  * - {@link #flashAttributes}
91985  *
91986  * ## Example usage:
91987  *
91988  *     var win = Ext.widget('window', {
91989  *         title: "It's a tiger!",
91990  *         layout: 'fit',
91991  *         width: 300,
91992  *         height: 300,
91993  *         x: 20,
91994  *         y: 20,
91995  *         resizable: true,
91996  *         items: {
91997  *             xtype: 'flash',
91998  *             url: 'tiger.swf'
91999  *         }
92000  *     });
92001  *     win.show();
92002  *
92003  * ## Express Install
92004  *
92005  * Adobe provides a tool called [Express Install](http://www.adobe.com/devnet/flashplayer/articles/express_install.html)
92006  * that offers users an easy way to upgrade their Flash player. If you wish to make use of this, you should set
92007  * the static EXPRESS\_INSTALL\_URL property to the location of your Express Install SWF file:
92008  *
92009  *     Ext.flash.Component.EXPRESS_INSTALL_URL = 'path/to/local/expressInstall.swf';
92010  *
92011  * @docauthor Jason Johnston <jason@sencha.com>
92012  */
92013 Ext.define('Ext.flash.Component', {
92014     extend: 'Ext.Component',
92015     alternateClassName: 'Ext.FlashComponent',
92016     alias: 'widget.flash',
92017
92018     /**
92019      * @cfg {String} flashVersion
92020      * Indicates the version the flash content was published for. Defaults to <tt>'9.0.115'</tt>.
92021      */
92022     flashVersion : '9.0.115',
92023
92024     /**
92025      * @cfg {String} backgroundColor
92026      * The background color of the SWF movie. Defaults to <tt>'#ffffff'</tt>.
92027      */
92028     backgroundColor: '#ffffff',
92029
92030     /**
92031      * @cfg {String} wmode
92032      * The wmode of the flash object. This can be used to control layering. Defaults to <tt>'opaque'</tt>.
92033      * Set to 'transparent' to ignore the {@link #backgroundColor} and make the background of the Flash
92034      * movie transparent.
92035      */
92036     wmode: 'opaque',
92037
92038     /**
92039      * @cfg {Object} flashVars
92040      * A set of key value pairs to be passed to the flash object as flash variables. Defaults to <tt>undefined</tt>.
92041      */
92042
92043     /**
92044      * @cfg {Object} flashParams
92045      * A set of key value pairs to be passed to the flash object as parameters. Possible parameters can be found here:
92046      * http://kb2.adobe.com/cps/127/tn_12701.html Defaults to <tt>undefined</tt>.
92047      */
92048
92049     /**
92050      * @cfg {Object} flashAttributes
92051      * A set of key value pairs to be passed to the flash object as attributes. Defaults to <tt>undefined</tt>.
92052      */
92053
92054     /**
92055      * @cfg {String} url
92056      * The URL of the SWF file to include. Required.
92057      */
92058
92059     /**
92060      * @cfg {String/Number} swfWidth The width of the embedded SWF movie inside the component. Defaults to "100%"
92061      * so that the movie matches the width of the component.
92062      */
92063     swfWidth: '100%',
92064
92065     /**
92066      * @cfg {String/Number} swfHeight The height of the embedded SWF movie inside the component. Defaults to "100%"
92067      * so that the movie matches the height of the component.
92068      */
92069     swfHeight: '100%',
92070
92071     /**
92072      * @cfg {Boolean} expressInstall
92073      * True to prompt the user to install flash if not installed. Note that this uses
92074      * Ext.FlashComponent.EXPRESS_INSTALL_URL, which should be set to the local resource. Defaults to <tt>false</tt>.
92075      */
92076     expressInstall: false,
92077
92078     /**
92079      * @property swf
92080      * @type {Ext.Element}
92081      * A reference to the object or embed element into which the SWF file is loaded. Only
92082      * populated after the component is rendered and the SWF has been successfully embedded.
92083      */
92084
92085     // Have to create a placeholder div with the swfId, which SWFObject will replace with the object/embed element.
92086     renderTpl: ['<div id="{swfId}"></div>'],
92087
92088     initComponent: function() {
92089
92090         this.callParent();
92091         this.addEvents(
92092             /**
92093              * @event success
92094              * Fired when the Flash movie has been successfully embedded
92095              * @param {Ext.flash.Component} this
92096              */
92097             'success',
92098
92099             /**
92100              * @event failure
92101              * Fired when the Flash movie embedding fails
92102              * @param {Ext.flash.Component} this
92103              */
92104             'failure'
92105         );
92106     },
92107
92108     onRender: function() {
92109         var me = this,
92110             params, vars, undef,
92111             swfId = me.getSwfId();
92112
92113         me.renderData.swfId = swfId;
92114
92115         me.callParent(arguments);
92116
92117         params = Ext.apply({
92118             allowScriptAccess: 'always',
92119             bgcolor: me.backgroundColor,
92120             wmode: me.wmode
92121         }, me.flashParams);
92122
92123         vars = Ext.apply({
92124             allowedDomain: document.location.hostname
92125         }, me.flashVars);
92126
92127         new swfobject.embedSWF(
92128             me.url,
92129             swfId,
92130             me.swfWidth,
92131             me.swfHeight,
92132             me.flashVersion,
92133             me.expressInstall ? me.statics.EXPRESS_INSTALL_URL : undef,
92134             vars,
92135             params,
92136             me.flashAttributes,
92137             Ext.bind(me.swfCallback, me)
92138         );
92139     },
92140
92141     /**
92142      * @private
92143      * The callback method for handling an embedding success or failure by SWFObject
92144      * @param {Object} e The event object passed by SWFObject - see http://code.google.com/p/swfobject/wiki/api
92145      */
92146     swfCallback: function(e) {
92147         var me = this;
92148         if (e.success) {
92149             me.swf = Ext.get(e.ref);
92150             me.onSuccess();
92151             me.fireEvent('success', me);
92152         } else {
92153             me.onFailure();
92154             me.fireEvent('failure', me);
92155         }
92156     },
92157
92158     /**
92159      * Retrieve the id of the SWF object/embed element
92160      */
92161     getSwfId: function() {
92162         return this.swfId || (this.swfId = "extswf" + this.getAutoId());
92163     },
92164
92165     onSuccess: function() {
92166         // swfobject forces visiblity:visible on the swf element, which prevents it 
92167         // from getting hidden when an ancestor is given visibility:hidden.
92168         this.swf.setStyle('visibility', 'inherit');
92169     },
92170
92171     onFailure: Ext.emptyFn,
92172
92173     beforeDestroy: function() {
92174         var me = this,
92175             swf = me.swf;
92176         if (swf) {
92177             swfobject.removeSWF(me.getSwfId());
92178             Ext.destroy(swf);
92179             delete me.swf;
92180         }
92181         me.callParent();
92182     },
92183
92184     statics: {
92185         /**
92186          * Sets the url for installing flash if it doesn't exist. This should be set to a local resource.
92187          * See http://www.adobe.com/devnet/flashplayer/articles/express_install.html for details.
92188          * @static
92189          * @type String
92190          */
92191         EXPRESS_INSTALL_URL: 'http:/' + '/swfobject.googlecode.com/svn/trunk/swfobject/expressInstall.swf'
92192     }
92193 });
92194
92195 /**
92196  * @class Ext.form.action.Action
92197  * @extends Ext.Base
92198  * <p>The subclasses of this class provide actions to perform upon {@link Ext.form.Basic Form}s.</p>
92199  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
92200  * the Form needs to perform an action such as submit or load. The Configuration options
92201  * listed for this class are set through the Form's action methods: {@link Ext.form.Basic#submit submit},
92202  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}</p>
92203  * <p>The instance of Action which performed the action is passed to the success
92204  * and failure callbacks of the Form's action methods ({@link Ext.form.Basic#submit submit},
92205  * {@link Ext.form.Basic#load load} and {@link Ext.form.Basic#doAction doAction}),
92206  * and to the {@link Ext.form.Basic#actioncomplete actioncomplete} and
92207  * {@link Ext.form.Basic#actionfailed actionfailed} event handlers.</p>
92208  */
92209 Ext.define('Ext.form.action.Action', {
92210     alternateClassName: 'Ext.form.Action',
92211
92212     /**
92213      * @cfg {Ext.form.Basic} form The {@link Ext.form.Basic BasicForm} instance that
92214      * is invoking this Action. Required.
92215      */
92216
92217     /**
92218      * @cfg {String} url The URL that the Action is to invoke. Will default to the {@link Ext.form.Basic#url url}
92219      * configured on the {@link #form}.
92220      */
92221
92222     /**
92223      * @cfg {Boolean} reset When set to <tt><b>true</b></tt>, causes the Form to be
92224      * {@link Ext.form.Basic#reset reset} on Action success. If specified, this happens
92225      * before the {@link #success} callback is called and before the Form's
92226      * {@link Ext.form.Basic#actioncomplete actioncomplete} event fires.
92227      */
92228
92229     /**
92230      * @cfg {String} method The HTTP method to use to access the requested URL. Defaults to the
92231      * {@link Ext.form.Basic#method BasicForm's method}, or 'POST' if not specified.
92232      */
92233
92234     /**
92235      * @cfg {Object/String} params <p>Extra parameter values to pass. These are added to the Form's
92236      * {@link Ext.form.Basic#baseParams} and passed to the specified URL along with the Form's
92237      * input fields.</p>
92238      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p>
92239      */
92240
92241     /**
92242      * @cfg {Object} headers <p>Extra headers to be sent in the AJAX request for submit and load actions. See
92243      * {@link Ext.data.proxy.Ajax#headers}.</p>
92244      */
92245
92246     /**
92247      * @cfg {Number} timeout The number of seconds to wait for a server response before
92248      * failing with the {@link #failureType} as {@link Ext.form.action.Action#CONNECT_FAILURE}. If not specified,
92249      * defaults to the configured <tt>{@link Ext.form.Basic#timeout timeout}</tt> of the
92250      * {@link #form}.
92251      */
92252
92253     /**
92254      * @cfg {Function} success The function to call when a valid success return packet is received.
92255      * The function is passed the following parameters:<ul class="mdetail-params">
92256      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
92257      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. The {@link #result}
92258      * property of this object may be examined to perform custom postprocessing.</div></li>
92259      * </ul>
92260      */
92261
92262     /**
92263      * @cfg {Function} failure The function to call when a failure packet was received, or when an
92264      * error ocurred in the Ajax communication.
92265      * The function is passed the following parameters:<ul class="mdetail-params">
92266      * <li><b>form</b> : Ext.form.Basic<div class="sub-desc">The form that requested the action</div></li>
92267      * <li><b>action</b> : Ext.form.action.Action<div class="sub-desc">The Action class. If an Ajax
92268      * error ocurred, the failure type will be in {@link #failureType}. The {@link #result}
92269      * property of this object may be examined to perform custom postprocessing.</div></li>
92270      * </ul>
92271      */
92272
92273     /**
92274      * @cfg {Object} scope The scope in which to call the configured <tt>success</tt> and <tt>failure</tt>
92275      * callback functions (the <tt>this</tt> reference for the callback functions).
92276      */
92277
92278     /**
92279      * @cfg {String} waitMsg The message to be displayed by a call to {@link Ext.window.MessageBox#wait}
92280      * during the time the action is being processed.
92281      */
92282
92283     /**
92284      * @cfg {String} waitTitle The title to be displayed by a call to {@link Ext.window.MessageBox#wait}
92285      * during the time the action is being processed.
92286      */
92287
92288     /**
92289      * @cfg {Boolean} submitEmptyText If set to <tt>true</tt>, the emptyText value will be sent with the form
92290      * when it is submitted. Defaults to <tt>true</tt>.
92291      */
92292     submitEmptyText : true,
92293     /**
92294      * @property type
92295      * The type of action this Action instance performs.
92296      * Currently only "submit" and "load" are supported.
92297      * @type {String}
92298      */
92299
92300     /**
92301      * The type of failure detected will be one of these: {@link Ext.form.action.Action#CLIENT_INVALID},
92302      * {@link Ext.form.action.Action#SERVER_INVALID}, {@link Ext.form.action.Action#CONNECT_FAILURE}, or
92303      * {@link Ext.form.action.Action#LOAD_FAILURE}.  Usage:
92304      * <pre><code>
92305 var fp = new Ext.form.Panel({
92306 ...
92307 buttons: [{
92308     text: 'Save',
92309     formBind: true,
92310     handler: function(){
92311         if(fp.getForm().isValid()){
92312             fp.getForm().submit({
92313                 url: 'form-submit.php',
92314                 waitMsg: 'Submitting your data...',
92315                 success: function(form, action){
92316                     // server responded with success = true
92317                     var result = action.{@link #result};
92318                 },
92319                 failure: function(form, action){
92320                     if (action.{@link #failureType} === {@link Ext.form.action.Action#CONNECT_FAILURE}) {
92321                         Ext.Msg.alert('Error',
92322                             'Status:'+action.{@link #response}.status+': '+
92323                             action.{@link #response}.statusText);
92324                     }
92325                     if (action.failureType === {@link Ext.form.action.Action#SERVER_INVALID}){
92326                         // server responded with success = false
92327                         Ext.Msg.alert('Invalid', action.{@link #result}.errormsg);
92328                     }
92329                 }
92330             });
92331         }
92332     }
92333 },{
92334     text: 'Reset',
92335     handler: function(){
92336         fp.getForm().reset();
92337     }
92338 }]
92339      * </code></pre>
92340      * @property failureType
92341      * @type {String}
92342      */
92343
92344     /**
92345      * The raw XMLHttpRequest object used to perform the action.
92346      * @property response
92347      * @type {Object}
92348      */
92349
92350     /**
92351      * The decoded response object containing a boolean <tt>success</tt> property and
92352      * other, action-specific properties.
92353      * @property result
92354      * @type {Object}
92355      */
92356
92357     /**
92358      * Creates new Action.
92359      * @param {Object} config (optional) Config object.
92360      */
92361     constructor: function(config) {
92362         if (config) {
92363             Ext.apply(this, config);
92364         }
92365
92366         // Normalize the params option to an Object
92367         var params = config.params;
92368         if (Ext.isString(params)) {
92369             this.params = Ext.Object.fromQueryString(params);
92370         }
92371     },
92372
92373     /**
92374      * Invokes this action using the current configuration.
92375      */
92376     run: Ext.emptyFn,
92377
92378     /**
92379      * @private
92380      * @method onSuccess
92381      * Callback method that gets invoked when the action completes successfully. Must be implemented by subclasses.
92382      * @param {Object} response
92383      */
92384
92385     /**
92386      * @private
92387      * @method handleResponse
92388      * Handles the raw response and builds a result object from it. Must be implemented by subclasses.
92389      * @param {Object} response
92390      */
92391
92392     /**
92393      * @private
92394      * Handles a failure response.
92395      * @param {Object} response
92396      */
92397     onFailure : function(response){
92398         this.response = response;
92399         this.failureType = Ext.form.action.Action.CONNECT_FAILURE;
92400         this.form.afterAction(this, false);
92401     },
92402
92403     /**
92404      * @private
92405      * Validates that a response contains either responseText or responseXML and invokes
92406      * {@link #handleResponse} to build the result object.
92407      * @param {Object} response The raw response object.
92408      * @return {Object/Boolean} result The result object as built by handleResponse, or <tt>true</tt> if
92409      *                         the response had empty responseText and responseXML.
92410      */
92411     processResponse : function(response){
92412         this.response = response;
92413         if (!response.responseText && !response.responseXML) {
92414             return true;
92415         }
92416         return (this.result = this.handleResponse(response));
92417     },
92418
92419     /**
92420      * @private
92421      * Build the URL for the AJAX request. Used by the standard AJAX submit and load actions.
92422      * @return {String} The URL.
92423      */
92424     getUrl: function() {
92425         return this.url || this.form.url;
92426     },
92427
92428     /**
92429      * @private
92430      * Determine the HTTP method to be used for the request.
92431      * @return {String} The HTTP method
92432      */
92433     getMethod: function() {
92434         return (this.method || this.form.method || 'POST').toUpperCase();
92435     },
92436
92437     /**
92438      * @private
92439      * Get the set of parameters specified in the BasicForm's baseParams and/or the params option.
92440      * Items in params override items of the same name in baseParams.
92441      * @return {Object} the full set of parameters
92442      */
92443     getParams: function() {
92444         return Ext.apply({}, this.params, this.form.baseParams);
92445     },
92446
92447     /**
92448      * @private
92449      * Creates a callback object.
92450      */
92451     createCallback: function() {
92452         var me = this,
92453             undef,
92454             form = me.form;
92455         return {
92456             success: me.onSuccess,
92457             failure: me.onFailure,
92458             scope: me,
92459             timeout: (this.timeout * 1000) || (form.timeout * 1000),
92460             upload: form.fileUpload ? me.onSuccess : undef
92461         };
92462     },
92463
92464     statics: {
92465         /**
92466          * @property CLIENT_INVALID
92467          * Failure type returned when client side validation of the Form fails
92468          * thus aborting a submit action. Client side validation is performed unless
92469          * {@link Ext.form.action.Submit#clientValidation} is explicitly set to <tt>false</tt>.
92470          * @type {String}
92471          * @static
92472          */
92473         CLIENT_INVALID: 'client',
92474
92475         /**
92476          * @property SERVER_INVALID
92477          * <p>Failure type returned when server side processing fails and the {@link #result}'s
92478          * <tt>success</tt> property is set to <tt>false</tt>.</p>
92479          * <p>In the case of a form submission, field-specific error messages may be returned in the
92480          * {@link #result}'s <tt>errors</tt> property.</p>
92481          * @type {String}
92482          * @static
92483          */
92484         SERVER_INVALID: 'server',
92485
92486         /**
92487          * @property CONNECT_FAILURE
92488          * Failure type returned when a communication error happens when attempting
92489          * to send a request to the remote server. The {@link #response} may be examined to
92490          * provide further information.
92491          * @type {String}
92492          * @static
92493          */
92494         CONNECT_FAILURE: 'connect',
92495
92496         /**
92497          * @property LOAD_FAILURE
92498          * Failure type returned when the response's <tt>success</tt>
92499          * property is set to <tt>false</tt>, or no field values are returned in the response's
92500          * <tt>data</tt> property.
92501          * @type {String}
92502          * @static
92503          */
92504         LOAD_FAILURE: 'load'
92505
92506
92507     }
92508 });
92509
92510 /**
92511  * @class Ext.form.action.Submit
92512  * @extends Ext.form.action.Action
92513  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s
92514  * and processes the returned response.</p>
92515  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
92516  * {@link Ext.form.Basic#submit submit}ting.</p>
92517  * <p><u><b>Response Packet Criteria</b></u></p>
92518  * <p>A response packet may contain:
92519  * <div class="mdetail-params"><ul>
92520  * <li><b><code>success</code></b> property : Boolean
92521  * <div class="sub-desc">The <code>success</code> property is required.</div></li>
92522  * <li><b><code>errors</code></b> property : Object
92523  * <div class="sub-desc"><div class="sub-desc">The <code>errors</code> property,
92524  * which is optional, contains error messages for invalid fields.</div></li>
92525  * </ul></div>
92526  * <p><u><b>JSON Packets</b></u></p>
92527  * <p>By default, response packets are assumed to be JSON, so a typical response
92528  * packet may look like this:</p><pre><code>
92529 {
92530     success: false,
92531     errors: {
92532         clientCode: "Client not found",
92533         portOfLoading: "This field must not be null"
92534     }
92535 }</code></pre>
92536  * <p>Other data may be placed into the response for processing by the {@link Ext.form.Basic}'s callback
92537  * or event handler methods. The object decoded from this JSON is available in the
92538  * {@link Ext.form.action.Action#result result} property.</p>
92539  * <p>Alternatively, if an {@link Ext.form.Basic#errorReader errorReader} is specified as an {@link Ext.data.reader.Xml XmlReader}:</p><pre><code>
92540     errorReader: new Ext.data.reader.Xml({
92541             record : 'field',
92542             success: '@success'
92543         }, [
92544             'id', 'msg'
92545         ]
92546     )
92547 </code></pre>
92548  * <p>then the results may be sent back in XML format:</p><pre><code>
92549 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
92550 &lt;message success="false"&gt;
92551 &lt;errors&gt;
92552     &lt;field&gt;
92553         &lt;id&gt;clientCode&lt;/id&gt;
92554         &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;
92555     &lt;/field&gt;
92556     &lt;field&gt;
92557         &lt;id&gt;portOfLoading&lt;/id&gt;
92558         &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;
92559     &lt;/field&gt;
92560 &lt;/errors&gt;
92561 &lt;/message&gt;
92562 </code></pre>
92563  * <p>Other elements may be placed into the response XML for processing by the {@link Ext.form.Basic}'s callback
92564  * or event handler methods. The XML document is available in the {@link Ext.form.Basic#errorReader errorReader}'s
92565  * {@link Ext.data.reader.Xml#xmlData xmlData} property.</p>
92566  */
92567 Ext.define('Ext.form.action.Submit', {
92568     extend:'Ext.form.action.Action',
92569     alternateClassName: 'Ext.form.Action.Submit',
92570     alias: 'formaction.submit',
92571
92572     type: 'submit',
92573
92574     /**
92575      * @cfg {Boolean} clientValidation Determines whether a Form's fields are validated
92576      * in a final call to {@link Ext.form.Basic#isValid isValid} prior to submission.
92577      * Pass <tt>false</tt> in the Form's submit options to prevent this. Defaults to true.
92578      */
92579
92580     // inherit docs
92581     run : function(){
92582         var form = this.form;
92583         if (this.clientValidation === false || form.isValid()) {
92584             this.doSubmit();
92585         } else {
92586             // client validation failed
92587             this.failureType = Ext.form.action.Action.CLIENT_INVALID;
92588             form.afterAction(this, false);
92589         }
92590     },
92591
92592     /**
92593      * @private
92594      * Perform the submit of the form data.
92595      */
92596     doSubmit: function() {
92597         var formEl,
92598             ajaxOptions = Ext.apply(this.createCallback(), {
92599                 url: this.getUrl(),
92600                 method: this.getMethod(),
92601                 headers: this.headers
92602             });
92603
92604         // For uploads we need to create an actual form that contains the file upload fields,
92605         // and pass that to the ajax call so it can do its iframe-based submit method.
92606         if (this.form.hasUpload()) {
92607             formEl = ajaxOptions.form = this.buildForm();
92608             ajaxOptions.isUpload = true;
92609         } else {
92610             ajaxOptions.params = this.getParams();
92611         }
92612
92613         Ext.Ajax.request(ajaxOptions);
92614
92615         if (formEl) {
92616             Ext.removeNode(formEl);
92617         }
92618     },
92619
92620     /**
92621      * @private
92622      * Build the full set of parameters from the field values plus any additional configured params.
92623      */
92624     getParams: function() {
92625         var nope = false,
92626             configParams = this.callParent(),
92627             fieldParams = this.form.getValues(nope, nope, this.submitEmptyText !== nope);
92628         return Ext.apply({}, fieldParams, configParams);
92629     },
92630
92631     /**
92632      * @private
92633      * Build a form element containing fields corresponding to all the parameters to be
92634      * submitted (everything returned by {@link #getParams}.
92635      * NOTE: the form element is automatically added to the DOM, so any code that uses
92636      * it must remove it from the DOM after finishing with it.
92637      * @return HTMLFormElement
92638      */
92639     buildForm: function() {
92640         var fieldsSpec = [],
92641             formSpec,
92642             formEl,
92643             basicForm = this.form,
92644             params = this.getParams(),
92645             uploadFields = [];
92646
92647         basicForm.getFields().each(function(field) {
92648             if (field.isFileUpload()) {
92649                 uploadFields.push(field);
92650             }
92651         });
92652
92653         function addField(name, val) {
92654             fieldsSpec.push({
92655                 tag: 'input',
92656                 type: 'hidden',
92657                 name: name,
92658                 value: Ext.String.htmlEncode(val)
92659             });
92660         }
92661
92662         // Add the form field values
92663         Ext.iterate(params, function(key, val) {
92664             if (Ext.isArray(val)) {
92665                 Ext.each(val, function(v) {
92666                     addField(key, v);
92667                 });
92668             } else {
92669                 addField(key, val);
92670             }
92671         });
92672
92673         formSpec = {
92674             tag: 'form',
92675             action: this.getUrl(),
92676             method: this.getMethod(),
92677             target: this.target || '_self',
92678             style: 'display:none',
92679             cn: fieldsSpec
92680         };
92681
92682         // Set the proper encoding for file uploads
92683         if (uploadFields.length) {
92684             formSpec.encoding = formSpec.enctype = 'multipart/form-data';
92685         }
92686
92687         // Create the form
92688         formEl = Ext.DomHelper.append(Ext.getBody(), formSpec);
92689
92690         // Special handling for file upload fields: since browser security measures prevent setting
92691         // their values programatically, and prevent carrying their selected values over when cloning,
92692         // we have to move the actual field instances out of their components and into the form.
92693         Ext.Array.each(uploadFields, function(field) {
92694             if (field.rendered) { // can only have a selected file value after being rendered
92695                 formEl.appendChild(field.extractFileInput());
92696             }
92697         });
92698
92699         return formEl;
92700     },
92701
92702
92703
92704     /**
92705      * @private
92706      */
92707     onSuccess: function(response) {
92708         var form = this.form,
92709             success = true,
92710             result = this.processResponse(response);
92711         if (result !== true && !result.success) {
92712             if (result.errors) {
92713                 form.markInvalid(result.errors);
92714             }
92715             this.failureType = Ext.form.action.Action.SERVER_INVALID;
92716             success = false;
92717         }
92718         form.afterAction(this, success);
92719     },
92720
92721     /**
92722      * @private
92723      */
92724     handleResponse: function(response) {
92725         var form = this.form,
92726             errorReader = form.errorReader,
92727             rs, errors, i, len, records;
92728         if (errorReader) {
92729             rs = errorReader.read(response);
92730             records = rs.records;
92731             errors = [];
92732             if (records) {
92733                 for(i = 0, len = records.length; i < len; i++) {
92734                     errors[i] = records[i].data;
92735                 }
92736             }
92737             if (errors.length < 1) {
92738                 errors = null;
92739             }
92740             return {
92741                 success : rs.success,
92742                 errors : errors
92743             };
92744         }
92745         return Ext.decode(response.responseText);
92746     }
92747 });
92748
92749 /**
92750  * @class Ext.util.ComponentDragger
92751  * @extends Ext.dd.DragTracker
92752  * <p>A subclass of Ext.dd.DragTracker which handles dragging any Component.</p>
92753  * <p>This is configured with a Component to be made draggable, and a config object for the
92754  * {@link Ext.dd.DragTracker} class.</p>
92755  * <p>A {@link #delegate} may be provided which may be either the element to use as the mousedown target
92756  * or a {@link Ext.DomQuery} selector to activate multiple mousedown targets.</p>
92757  */
92758 Ext.define('Ext.util.ComponentDragger', {
92759
92760     /**
92761      * @cfg {Boolean} constrain
92762      * Specify as <code>true</code> to constrain the Component to within the bounds of the {@link #constrainTo} region.
92763      */
92764
92765     /**
92766      * @cfg {String/Ext.Element} delegate
92767      * Optional. <p>A {@link Ext.DomQuery DomQuery} selector which identifies child elements within the Component's encapsulating
92768      * Element which are the drag handles. This limits dragging to only begin when the matching elements are mousedowned.</p>
92769      * <p>This may also be a specific child element within the Component's encapsulating element to use as the drag handle.</p>
92770      */
92771
92772     /**
92773      * @cfg {Boolean} constrainDelegate
92774      * Specify as <code>true</code> to constrain the drag handles within the {@link #constrainTo} region.
92775      */
92776
92777     extend: 'Ext.dd.DragTracker',
92778
92779     autoStart: 500,
92780
92781     /**
92782      * Creates new ComponentDragger.
92783      * @param {Object} comp The Component to provide dragging for.
92784      * @param {Object} config (optional) Config object
92785      */
92786     constructor: function(comp, config) {
92787         this.comp = comp;
92788         this.initialConstrainTo = config.constrainTo;
92789         this.callParent([ config ]);
92790     },
92791
92792     onStart: function(e) {
92793         var me = this,
92794             comp = me.comp;
92795
92796         // Cache the start [X, Y] array
92797         this.startPosition = comp.getPosition();
92798
92799         // If client Component has a ghost method to show a lightweight version of itself
92800         // then use that as a drag proxy unless configured to liveDrag.
92801         if (comp.ghost && !comp.liveDrag) {
92802              me.proxy = comp.ghost();
92803              me.dragTarget = me.proxy.header.el;
92804         }
92805
92806         // Set the constrainTo Region before we start dragging.
92807         if (me.constrain || me.constrainDelegate) {
92808             me.constrainTo = me.calculateConstrainRegion();
92809         }
92810     },
92811
92812     calculateConstrainRegion: function() {
92813         var me = this,
92814             comp = me.comp,
92815             c = me.initialConstrainTo,
92816             delegateRegion,
92817             elRegion,
92818             shadowSize = comp.el.shadow ? comp.el.shadow.offset : 0;
92819
92820         // The configured constrainTo might be a Region or an element
92821         if (!(c instanceof Ext.util.Region)) {
92822             c =  Ext.fly(c).getViewRegion();
92823         }
92824
92825         // Reduce the constrain region to allow for shadow
92826         if (shadowSize) {
92827             c.adjust(0, -shadowSize, -shadowSize, shadowSize);
92828         }
92829
92830         // If they only want to constrain the *delegate* to within the constrain region,
92831         // adjust the region to be larger based on the insets of the delegate from the outer
92832         // edges of the Component.
92833         if (!me.constrainDelegate) {
92834             delegateRegion = Ext.fly(me.dragTarget).getRegion();
92835             elRegion = me.proxy ? me.proxy.el.getRegion() : comp.el.getRegion();
92836
92837             c.adjust(
92838                 delegateRegion.top - elRegion.top,
92839                 delegateRegion.right - elRegion.right,
92840                 delegateRegion.bottom - elRegion.bottom,
92841                 delegateRegion.left - elRegion.left
92842             );
92843         }
92844         return c;
92845     },
92846
92847     // Move either the ghost Component or the target Component to its new position on drag
92848     onDrag: function(e) {
92849         var me = this,
92850             comp = (me.proxy && !me.comp.liveDrag) ? me.proxy : me.comp,
92851             offset = me.getOffset(me.constrain || me.constrainDelegate ? 'dragTarget' : null);
92852
92853         comp.setPosition(me.startPosition[0] + offset[0], me.startPosition[1] + offset[1]);
92854     },
92855
92856     onEnd: function(e) {
92857         if (this.proxy && !this.comp.liveDrag) {
92858             this.comp.unghost();
92859         }
92860     }
92861 });
92862 /**
92863  * A mixin which allows a component to be configured and decorated with a label and/or error message as is
92864  * common for form fields. This is used by e.g. Ext.form.field.Base and Ext.form.FieldContainer
92865  * to let them be managed by the Field layout.
92866  *
92867  * NOTE: This mixin is mainly for internal library use and most users should not need to use it directly. It
92868  * is more likely you will want to use one of the component classes that import this mixin, such as
92869  * Ext.form.field.Base or Ext.form.FieldContainer.
92870  *
92871  * Use of this mixin does not make a component a field in the logical sense, meaning it does not provide any
92872  * logic or state related to values or validation; that is handled by the related Ext.form.field.Field
92873  * mixin. These two mixins may be used separately (for example Ext.form.FieldContainer is Labelable but not a
92874  * Field), or in combination (for example Ext.form.field.Base implements both and has logic for connecting the
92875  * two.)
92876  *
92877  * Component classes which use this mixin should use the Field layout
92878  * or a derivation thereof to properly size and position the label and message according to the component config.
92879  * They must also call the {@link #initLabelable} method during component initialization to ensure the mixin gets
92880  * set up correctly.
92881  *
92882  * @docauthor Jason Johnston <jason@sencha.com>
92883  */
92884 Ext.define("Ext.form.Labelable", {
92885     requires: ['Ext.XTemplate'],
92886
92887     /**
92888      * @cfg {String/String[]/Ext.XTemplate} labelableRenderTpl
92889      * The rendering template for the field decorations. Component classes using this mixin should include
92890      * logic to use this as their {@link Ext.AbstractComponent#renderTpl renderTpl}, and implement the
92891      * {@link #getSubTplMarkup} method to generate the field body content.
92892      */
92893     labelableRenderTpl: [
92894         '<tpl if="!hideLabel && !(!fieldLabel && hideEmptyLabel)">',
92895             '<label id="{id}-labelEl"<tpl if="inputId"> for="{inputId}"</tpl> class="{labelCls}"',
92896                 '<tpl if="labelStyle"> style="{labelStyle}"</tpl>>',
92897                 '<tpl if="fieldLabel">{fieldLabel}{labelSeparator}</tpl>',
92898             '</label>',
92899         '</tpl>',
92900         '<div class="{baseBodyCls} {fieldBodyCls}" id="{id}-bodyEl" role="presentation">{subTplMarkup}</div>',
92901         '<div id="{id}-errorEl" class="{errorMsgCls}" style="display:none"></div>',
92902         '<div class="{clearCls}" role="presentation"><!-- --></div>',
92903         {
92904             compiled: true,
92905             disableFormats: true
92906         }
92907     ],
92908
92909     /**
92910      * @cfg {Ext.XTemplate} activeErrorsTpl
92911      * The template used to format the Array of error messages passed to {@link #setActiveErrors}
92912      * into a single HTML string. By default this renders each message as an item in an unordered list.
92913      */
92914     activeErrorsTpl: [
92915         '<tpl if="errors && errors.length">',
92916             '<ul><tpl for="errors"><li<tpl if="xindex == xcount"> class="last"</tpl>>{.}</li></tpl></ul>',
92917         '</tpl>'
92918     ],
92919
92920     /**
92921      * @property isFieldLabelable
92922      * @type Boolean
92923      * Flag denoting that this object is labelable as a field. Always true.
92924      */
92925     isFieldLabelable: true,
92926
92927     /**
92928      * @cfg {String} [formItemCls='x-form-item']
92929      * A CSS class to be applied to the outermost element to denote that it is participating in the form
92930      * field layout.
92931      */
92932     formItemCls: Ext.baseCSSPrefix + 'form-item',
92933
92934     /**
92935      * @cfg {String} [labelCls='x-form-item-label']
92936      * The CSS class to be applied to the label element.
92937      * This (single) CSS class is used to formulate the renderSelector and drives the field
92938      * layout where it is concatenated with a hyphen ('-') and {@link #labelAlign}. To add
92939      * additional classes, use {@link #labelClsExtra}.
92940      */
92941     labelCls: Ext.baseCSSPrefix + 'form-item-label',
92942
92943     /**
92944      * @cfg {String} labelClsExtra
92945      * An optional string of one or more additional CSS classes to add to the label element.
92946      * Defaults to empty.
92947      */
92948
92949     /**
92950      * @cfg {String} [errorMsgCls='x-form-error-msg']
92951      * The CSS class to be applied to the error message element.
92952      */
92953     errorMsgCls: Ext.baseCSSPrefix + 'form-error-msg',
92954
92955     /**
92956      * @cfg {String} [baseBodyCls='x-form-item-body']
92957      * The CSS class to be applied to the body content element.
92958      */
92959     baseBodyCls: Ext.baseCSSPrefix + 'form-item-body',
92960
92961     /**
92962      * @cfg {String} fieldBodyCls
92963      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
92964      */
92965     fieldBodyCls: '',
92966
92967     /**
92968      * @cfg {String} [clearCls='x-clear']
92969      * The CSS class to be applied to the special clearing div rendered directly after the field
92970      * contents wrapper to provide field clearing.
92971      */
92972     clearCls: Ext.baseCSSPrefix + 'clear',
92973
92974     /**
92975      * @cfg {String} [invalidCls='x-form-invalid']
92976      * The CSS class to use when marking the component invalid.
92977      */
92978     invalidCls : Ext.baseCSSPrefix + 'form-invalid',
92979
92980     /**
92981      * @cfg {String} fieldLabel
92982      * The label for the field. It gets appended with the {@link #labelSeparator}, and its position
92983      * and sizing is determined by the {@link #labelAlign}, {@link #labelWidth}, and {@link #labelPad}
92984      * configs.
92985      */
92986     fieldLabel: undefined,
92987
92988     /**
92989      * @cfg {String} labelAlign
92990      * <p>Controls the position and alignment of the {@link #fieldLabel}. Valid values are:</p>
92991      * <ul>
92992      * <li><tt>"left"</tt> (the default) - The label is positioned to the left of the field, with its text
92993      * aligned to the left. Its width is determined by the {@link #labelWidth} config.</li>
92994      * <li><tt>"top"</tt> - The label is positioned above the field.</li>
92995      * <li><tt>"right"</tt> - The label is positioned to the left of the field, with its text aligned
92996      * to the right. Its width is determined by the {@link #labelWidth} config.</li>
92997      * </ul>
92998      */
92999     labelAlign : 'left',
93000
93001     /**
93002      * @cfg {Number} labelWidth
93003      * The width of the {@link #fieldLabel} in pixels. Only applicable if the {@link #labelAlign} is set
93004      * to "left" or "right".
93005      */
93006     labelWidth: 100,
93007
93008     /**
93009      * @cfg {Number} labelPad
93010      * The amount of space in pixels between the {@link #fieldLabel} and the input field.
93011      */
93012     labelPad : 5,
93013
93014     /**
93015      * @cfg {String} labelSeparator
93016      * Character(s) to be inserted at the end of the {@link #fieldLabel label text}.
93017      */
93018     labelSeparator : ':',
93019
93020     /**
93021      * @cfg {String} labelStyle
93022      * A CSS style specification string to apply directly to this field's label.
93023      */
93024
93025     /**
93026      * @cfg {Boolean} hideLabel
93027      * Set to true to completely hide the label element ({@link #fieldLabel} and {@link #labelSeparator}).
93028      * Also see {@link #hideEmptyLabel}, which controls whether space will be reserved for an empty fieldLabel.
93029      */
93030     hideLabel: false,
93031
93032     /**
93033      * @cfg {Boolean} hideEmptyLabel
93034      * <p>When set to <tt>true</tt>, the label element ({@link #fieldLabel} and {@link #labelSeparator}) will be
93035      * automatically hidden if the {@link #fieldLabel} is empty. Setting this to <tt>false</tt> will cause the empty
93036      * label element to be rendered and space to be reserved for it; this is useful if you want a field without a label
93037      * to line up with other labeled fields in the same form.</p>
93038      * <p>If you wish to unconditionall hide the label even if a non-empty fieldLabel is configured, then set
93039      * the {@link #hideLabel} config to <tt>true</tt>.</p>
93040      */
93041     hideEmptyLabel: true,
93042
93043     /**
93044      * @cfg {Boolean} preventMark
93045      * <tt>true</tt> to disable displaying any {@link #setActiveError error message} set on this object.
93046      */
93047     preventMark: false,
93048
93049     /**
93050      * @cfg {Boolean} autoFitErrors
93051      * Whether to adjust the component's body area to make room for 'side' or 'under'
93052      * {@link #msgTarget error messages}.
93053      */
93054     autoFitErrors: true,
93055
93056     /**
93057      * @cfg {String} msgTarget <p>The location where the error message text should display.
93058      * Must be one of the following values:</p>
93059      * <div class="mdetail-params"><ul>
93060      * <li><code>qtip</code> Display a quick tip containing the message when the user hovers over the field. This is the default.
93061      * <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>
93062      * <li><code>title</code> Display the message in a default browser title attribute popup.</li>
93063      * <li><code>under</code> Add a block div beneath the field containing the error message.</li>
93064      * <li><code>side</code> Add an error icon to the right of the field, displaying the message in a popup on hover.</li>
93065      * <li><code>none</code> Don't display any error message. This might be useful if you are implementing custom error display.</li>
93066      * <li><code>[element id]</code> Add the error message directly to the innerHTML of the specified element.</li>
93067      * </ul></div>
93068      */
93069     msgTarget: 'qtip',
93070
93071     /**
93072      * @cfg {String} activeError
93073      * If specified, then the component will be displayed with this value as its active error when
93074      * first rendered. Use {@link #setActiveError} or {@link #unsetActiveError} to
93075      * change it after component creation.
93076      */
93077
93078
93079     /**
93080      * Performs initialization of this mixin. Component classes using this mixin should call this method
93081      * during their own initialization.
93082      */
93083     initLabelable: function() {
93084         this.addCls(this.formItemCls);
93085
93086         this.addEvents(
93087             /**
93088              * @event errorchange
93089              * Fires when the active error message is changed via {@link #setActiveError}.
93090              * @param {Ext.form.Labelable} this
93091              * @param {String} error The active error message
93092              */
93093             'errorchange'
93094         );
93095     },
93096
93097     /**
93098      * Returns the label for the field. Defaults to simply returning the {@link #fieldLabel} config. Can be
93099      * overridden to provide
93100      * @return {String} The configured field label, or empty string if not defined
93101      */
93102     getFieldLabel: function() {
93103         return this.fieldLabel || '';
93104     },
93105
93106     /**
93107      * @protected
93108      * Generates the arguments for the field decorations {@link #labelableRenderTpl rendering template}.
93109      * @return {Object} The template arguments
93110      */
93111     getLabelableRenderData: function() {
93112         var me = this,
93113             labelAlign = me.labelAlign,
93114             labelCls = me.labelCls,
93115             labelClsExtra = me.labelClsExtra,
93116             labelPad = me.labelPad,
93117             labelStyle;
93118
93119         // Calculate label styles up front rather than in the Field layout for speed; this
93120         // is safe because label alignment/width/pad are not expected to change.
93121         if (labelAlign === 'top') {
93122             labelStyle = 'margin-bottom:' + labelPad + 'px;';
93123         } else {
93124             labelStyle = 'margin-right:' + labelPad + 'px;';
93125             // Add the width for border-box browsers; will be set by the Field layout for content-box
93126             if (Ext.isBorderBox) {
93127                 labelStyle += 'width:' + me.labelWidth + 'px;';
93128             }
93129         }
93130
93131         return Ext.copyTo(
93132             {
93133                 inputId: me.getInputId(),
93134                 fieldLabel: me.getFieldLabel(),
93135                 labelCls: labelClsExtra ? labelCls + ' ' + labelClsExtra : labelCls,
93136                 labelStyle: labelStyle + (me.labelStyle || ''),
93137                 subTplMarkup: me.getSubTplMarkup()
93138             },
93139             me,
93140             'hideLabel,hideEmptyLabel,fieldBodyCls,baseBodyCls,errorMsgCls,clearCls,labelSeparator',
93141             true
93142         );
93143     },
93144
93145     onLabelableRender: function () {
93146         this.addChildEls(
93147             /**
93148              * @property labelEl
93149              * @type Ext.Element
93150              * The label Element for this component. Only available after the component has been rendered.
93151              */
93152             'labelEl',
93153
93154             /**
93155              * @property bodyEl
93156              * @type Ext.Element
93157              * The div Element wrapping the component's contents. Only available after the component has been rendered.
93158              */
93159             'bodyEl',
93160
93161             /**
93162              * @property errorEl
93163              * @type Ext.Element
93164              * The div Element that will contain the component's error message(s). Note that depending on the
93165              * configured {@link #msgTarget}, this element may be hidden in favor of some other form of
93166              * presentation, but will always be present in the DOM for use by assistive technologies.
93167              */
93168             'errorEl'
93169         );
93170     },
93171
93172     /**
93173      * @protected
93174      * Gets the markup to be inserted into the outer template's bodyEl. Defaults to empty string, should
93175      * be implemented by classes including this mixin as needed.
93176      * @return {String} The markup to be inserted
93177      */
93178     getSubTplMarkup: function() {
93179         return '';
93180     },
93181
93182     /**
93183      * Get the input id, if any, for this component. This is used as the "for" attribute on the label element.
93184      * Implementing subclasses may also use this as e.g. the id for their own <tt>input</tt> element.
93185      * @return {String} The input id
93186      */
93187     getInputId: function() {
93188         return '';
93189     },
93190
93191     /**
93192      * Gets the active error message for this component, if any. This does not trigger
93193      * validation on its own, it merely returns any message that the component may already hold.
93194      * @return {String} The active error message on the component; if there is no error, an empty string is returned.
93195      */
93196     getActiveError : function() {
93197         return this.activeError || '';
93198     },
93199
93200     /**
93201      * Tells whether the field currently has an active error message. This does not trigger
93202      * validation on its own, it merely looks for any message that the component may already hold.
93203      * @return {Boolean}
93204      */
93205     hasActiveError: function() {
93206         return !!this.getActiveError();
93207     },
93208
93209     /**
93210      * Sets the active error message to the given string. This replaces the entire error message
93211      * contents with the given string. Also see {@link #setActiveErrors} which accepts an Array of
93212      * messages and formats them according to the {@link #activeErrorsTpl}.
93213      *
93214      * Note that this only updates the error message element's text and attributes, you'll have
93215      * to call doComponentLayout to actually update the field's layout to match. If the field extends
93216      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.
93217      *
93218      * @param {String} msg The error message
93219      */
93220     setActiveError: function(msg) {
93221         this.activeError = msg;
93222         this.activeErrors = [msg];
93223         this.renderActiveError();
93224     },
93225
93226     /**
93227      * Gets an Array of any active error messages currently applied to the field. This does not trigger
93228      * validation on its own, it merely returns any messages that the component may already hold.
93229      * @return {String[]} The active error messages on the component; if there are no errors, an empty Array is returned.
93230      */
93231     getActiveErrors: function() {
93232         return this.activeErrors || [];
93233     },
93234
93235     /**
93236      * Set the active error message to an Array of error messages. The messages are formatted into
93237      * a single message string using the {@link #activeErrorsTpl}. Also see {@link #setActiveError}
93238      * which allows setting the entire error contents with a single string.
93239      *
93240      * Note that this only updates the error message element's text and attributes, you'll have
93241      * to call doComponentLayout to actually update the field's layout to match. If the field extends
93242      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#markInvalid markInvalid} instead.
93243      *
93244      * @param {String[]} errors The error messages
93245      */
93246     setActiveErrors: function(errors) {
93247         this.activeErrors = errors;
93248         this.activeError = this.getTpl('activeErrorsTpl').apply({errors: errors});
93249         this.renderActiveError();
93250     },
93251
93252     /**
93253      * Clears the active error message(s).
93254      *
93255      * Note that this only clears the error message element's text and attributes, you'll have
93256      * to call doComponentLayout to actually update the field's layout to match. If the field extends
93257      * {@link Ext.form.field.Base} you should call {@link Ext.form.field.Base#clearInvalid clearInvalid} instead.
93258      */
93259     unsetActiveError: function() {
93260         delete this.activeError;
93261         delete this.activeErrors;
93262         this.renderActiveError();
93263     },
93264
93265     /**
93266      * @private
93267      * Updates the rendered DOM to match the current activeError. This only updates the content and
93268      * attributes, you'll have to call doComponentLayout to actually update the display.
93269      */
93270     renderActiveError: function() {
93271         var me = this,
93272             activeError = me.getActiveError(),
93273             hasError = !!activeError;
93274
93275         if (activeError !== me.lastActiveError) {
93276             me.fireEvent('errorchange', me, activeError);
93277             me.lastActiveError = activeError;
93278         }
93279
93280         if (me.rendered && !me.isDestroyed && !me.preventMark) {
93281             // Add/remove invalid class
93282             me.el[hasError ? 'addCls' : 'removeCls'](me.invalidCls);
93283
93284             // Update the aria-invalid attribute
93285             me.getActionEl().dom.setAttribute('aria-invalid', hasError);
93286
93287             // Update the errorEl with the error message text
93288             me.errorEl.dom.innerHTML = activeError;
93289         }
93290     },
93291
93292     /**
93293      * Applies a set of default configuration values to this Labelable instance. For each of the
93294      * properties in the given object, check if this component hasOwnProperty that config; if not
93295      * then it's inheriting a default value from its prototype and we should apply the default value.
93296      * @param {Object} defaults The defaults to apply to the object.
93297      */
93298     setFieldDefaults: function(defaults) {
93299         var me = this;
93300         Ext.iterate(defaults, function(key, val) {
93301             if (!me.hasOwnProperty(key)) {
93302                 me[key] = val;
93303             }
93304         });
93305     },
93306
93307     /**
93308      * @protected Calculate and return the natural width of the bodyEl. Override to provide custom logic.
93309      * Note for implementors: if at all possible this method should be overridden with a custom implementation
93310      * that can avoid anything that would cause the browser to reflow, e.g. querying offsetWidth.
93311      */
93312     getBodyNaturalWidth: function() {
93313         return this.bodyEl.getWidth();
93314     }
93315
93316 });
93317
93318 /**
93319  * @docauthor Jason Johnston <jason@sencha.com>
93320  *
93321  * This mixin provides a common interface for the logical behavior and state of form fields, including:
93322  *
93323  * - Getter and setter methods for field values
93324  * - Events and methods for tracking value and validity changes
93325  * - Methods for triggering validation
93326  *
93327  * **NOTE**: When implementing custom fields, it is most likely that you will want to extend the {@link Ext.form.field.Base}
93328  * component class rather than using this mixin directly, as BaseField contains additional logic for generating an
93329  * actual DOM complete with {@link Ext.form.Labelable label and error message} display and a form input field,
93330  * plus methods that bind the Field value getters and setters to the input field's value.
93331  *
93332  * If you do want to implement this mixin directly and don't want to extend {@link Ext.form.field.Base}, then
93333  * you will most likely want to override the following methods with custom implementations: {@link #getValue},
93334  * {@link #setValue}, and {@link #getErrors}. Other methods may be overridden as needed but their base
93335  * implementations should be sufficient for common cases. You will also need to make sure that {@link #initField}
93336  * is called during the component's initialization.
93337  */
93338 Ext.define('Ext.form.field.Field', {
93339     /**
93340      * @property {Boolean} isFormField
93341      * Flag denoting that this component is a Field. Always true.
93342      */
93343     isFormField : true,
93344
93345     /**
93346      * @cfg {Object} value
93347      * A value to initialize this field with.
93348      */
93349
93350     /**
93351      * @cfg {String} name
93352      * The name of the field. By default this is used as the parameter name when including the
93353      * {@link #getSubmitData field value} in a {@link Ext.form.Basic#submit form submit()}. To prevent the field from
93354      * being included in the form submit, set {@link #submitValue} to false.
93355      */
93356
93357     /**
93358      * @cfg {Boolean} disabled
93359      * True to disable the field. Disabled Fields will not be {@link Ext.form.Basic#submit submitted}.
93360      */
93361     disabled : false,
93362
93363     /**
93364      * @cfg {Boolean} submitValue
93365      * Setting this to false will prevent the field from being {@link Ext.form.Basic#submit submitted} even when it is
93366      * not disabled.
93367      */
93368     submitValue: true,
93369
93370     /**
93371      * @cfg {Boolean} validateOnChange
93372      * Specifies whether this field should be validated immediately whenever a change in its value is detected.
93373      * If the validation results in a change in the field's validity, a {@link #validitychange} event will be
93374      * fired. This allows the field to show feedback about the validity of its contents immediately as the user is
93375      * typing.
93376      *
93377      * When set to false, feedback will not be immediate. However the form will still be validated before submitting if
93378      * the clientValidation option to {@link Ext.form.Basic#doAction} is enabled, or if the field or form are validated
93379      * manually.
93380      *
93381      * See also {@link Ext.form.field.Base#checkChangeEvents} for controlling how changes to the field's value are
93382      * detected.
93383      */
93384     validateOnChange: true,
93385
93386     /**
93387      * @private
93388      */
93389     suspendCheckChange: 0,
93390
93391     /**
93392      * Initializes this Field mixin on the current instance. Components using this mixin should call this method during
93393      * their own initialization process.
93394      */
93395     initField: function() {
93396         this.addEvents(
93397             /**
93398              * @event change
93399              * Fires when a user-initiated change is detected in the value of the field.
93400              * @param {Ext.form.field.Field} this
93401              * @param {Object} newValue The new value
93402              * @param {Object} oldValue The original value
93403              */
93404             'change',
93405             /**
93406              * @event validitychange
93407              * Fires when a change in the field's validity is detected.
93408              * @param {Ext.form.field.Field} this
93409              * @param {Boolean} isValid Whether or not the field is now valid
93410              */
93411             'validitychange',
93412             /**
93413              * @event dirtychange
93414              * Fires when a change in the field's {@link #isDirty} state is detected.
93415              * @param {Ext.form.field.Field} this
93416              * @param {Boolean} isDirty Whether or not the field is now dirty
93417              */
93418             'dirtychange'
93419         );
93420
93421         this.initValue();
93422     },
93423
93424     /**
93425      * Initializes the field's value based on the initial config.
93426      */
93427     initValue: function() {
93428         var me = this;
93429
93430         /**
93431          * @property {Object} originalValue
93432          * The original value of the field as configured in the {@link #value} configuration, or as loaded by the last
93433          * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
93434          */
93435         me.originalValue = me.lastValue = me.value;
93436
93437         // Set the initial value - prevent validation on initial set
93438         me.suspendCheckChange++;
93439         me.setValue(me.value);
93440         me.suspendCheckChange--;
93441     },
93442
93443     /**
93444      * Returns the {@link Ext.form.field.Field#name name} attribute of the field. This is used as the parameter name
93445      * when including the field value in a {@link Ext.form.Basic#submit form submit()}.
93446      * @return {String} name The field {@link Ext.form.field.Field#name name}
93447      */
93448     getName: function() {
93449         return this.name;
93450     },
93451
93452     /**
93453      * Returns the current data value of the field. The type of value returned is particular to the type of the
93454      * particular field (e.g. a Date object for {@link Ext.form.field.Date}).
93455      * @return {Object} value The field value
93456      */
93457     getValue: function() {
93458         return this.value;
93459     },
93460
93461     /**
93462      * Sets a data value into the field and runs the change detection and validation.
93463      * @param {Object} value The value to set
93464      * @return {Ext.form.field.Field} this
93465      */
93466     setValue: function(value) {
93467         var me = this;
93468         me.value = value;
93469         me.checkChange();
93470         return me;
93471     },
93472
93473     /**
93474      * Returns whether two field {@link #getValue values} are logically equal. Field implementations may override this
93475      * to provide custom comparison logic appropriate for the particular field's data type.
93476      * @param {Object} value1 The first value to compare
93477      * @param {Object} value2 The second value to compare
93478      * @return {Boolean} True if the values are equal, false if inequal.
93479      */
93480     isEqual: function(value1, value2) {
93481         return String(value1) === String(value2);
93482     },
93483     
93484     /**
93485      * Returns whether two values are logically equal.
93486      * Similar to {@link #isEqual}, however null or undefined values will be treated as empty strings.
93487      * @private
93488      * @param {Object} value1 The first value to compare
93489      * @param {Object} value2 The second value to compare
93490      * @return {Boolean} True if the values are equal, false if inequal.
93491      */
93492     isEqualAsString: function(value1, value2){
93493         return String(Ext.value(value1, '')) === String(Ext.value(value2, ''));    
93494     },
93495
93496     /**
93497      * Returns the parameter(s) that would be included in a standard form submit for this field. Typically this will be
93498      * an object with a single name-value pair, the name being this field's {@link #getName name} and the value being
93499      * its current stringified value. More advanced field implementations may return more than one name-value pair.
93500      *
93501      * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
93502      * validated}.
93503      *
93504      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
93505      * strings if that particular name has multiple values. It can also return null if there are no parameters to be
93506      * submitted.
93507      */
93508     getSubmitData: function() {
93509         var me = this,
93510             data = null;
93511         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
93512             data = {};
93513             data[me.getName()] = '' + me.getValue();
93514         }
93515         return data;
93516     },
93517
93518     /**
93519      * Returns the value(s) that should be saved to the {@link Ext.data.Model} instance for this field, when {@link
93520      * Ext.form.Basic#updateRecord} is called. Typically this will be an object with a single name-value pair, the name
93521      * being this field's {@link #getName name} and the value being its current data value. More advanced field
93522      * implementations may return more than one name-value pair. The returned values will be saved to the corresponding
93523      * field names in the Model.
93524      *
93525      * Note that the values returned from this method are not guaranteed to have been successfully {@link #validate
93526      * validated}.
93527      *
93528      * @return {Object} A mapping of submit parameter names to values; each value should be a string, or an array of
93529      * strings if that particular name has multiple values. It can also return null if there are no parameters to be
93530      * submitted.
93531      */
93532     getModelData: function() {
93533         var me = this,
93534             data = null;
93535         if (!me.disabled && !me.isFileUpload()) {
93536             data = {};
93537             data[me.getName()] = me.getValue();
93538         }
93539         return data;
93540     },
93541
93542     /**
93543      * Resets the current field value to the originally loaded value and clears any validation messages. See {@link
93544      * Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
93545      */
93546     reset : function(){
93547         var me = this;
93548
93549         me.setValue(me.originalValue);
93550         me.clearInvalid();
93551         // delete here so we reset back to the original state
93552         delete me.wasValid;
93553     },
93554
93555     /**
93556      * Resets the field's {@link #originalValue} property so it matches the current {@link #getValue value}. This is
93557      * called by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues} if the form's
93558      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} property is set to true.
93559      */
93560     resetOriginalValue: function() {
93561         this.originalValue = this.getValue();
93562         this.checkDirty();
93563     },
93564
93565     /**
93566      * Checks whether the value of the field has changed since the last time it was checked.
93567      * If the value has changed, it:
93568      *
93569      * 1. Fires the {@link #change change event},
93570      * 2. Performs validation if the {@link #validateOnChange} config is enabled, firing the
93571      *    {@link #validitychange validitychange event} if the validity has changed, and
93572      * 3. Checks the {@link #isDirty dirty state} of the field and fires the {@link #dirtychange dirtychange event}
93573      *    if it has changed.
93574      */
93575     checkChange: function() {
93576         if (!this.suspendCheckChange) {
93577             var me = this,
93578                 newVal = me.getValue(),
93579                 oldVal = me.lastValue;
93580             if (!me.isEqual(newVal, oldVal) && !me.isDestroyed) {
93581                 me.lastValue = newVal;
93582                 me.fireEvent('change', me, newVal, oldVal);
93583                 me.onChange(newVal, oldVal);
93584             }
93585         }
93586     },
93587
93588     /**
93589      * @private
93590      * Called when the field's value changes. Performs validation if the {@link #validateOnChange}
93591      * config is enabled, and invokes the dirty check.
93592      */
93593     onChange: function(newVal, oldVal) {
93594         if (this.validateOnChange) {
93595             this.validate();
93596         }
93597         this.checkDirty();
93598     },
93599
93600     /**
93601      * Returns true if the value of this Field has been changed from its {@link #originalValue}.
93602      * Will always return false if the field is disabled.
93603      *
93604      * Note that if the owning {@link Ext.form.Basic form} was configured with
93605      * {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} then the {@link #originalValue} is updated when
93606      * the values are loaded by {@link Ext.form.Basic}.{@link Ext.form.Basic#setValues setValues}.
93607      * @return {Boolean} True if this field has been changed from its original value (and is not disabled),
93608      * false otherwise.
93609      */
93610     isDirty : function() {
93611         var me = this;
93612         return !me.disabled && !me.isEqual(me.getValue(), me.originalValue);
93613     },
93614
93615     /**
93616      * Checks the {@link #isDirty} state of the field and if it has changed since the last time it was checked,
93617      * fires the {@link #dirtychange} event.
93618      */
93619     checkDirty: function() {
93620         var me = this,
93621             isDirty = me.isDirty();
93622         if (isDirty !== me.wasDirty) {
93623             me.fireEvent('dirtychange', me, isDirty);
93624             me.onDirtyChange(isDirty);
93625             me.wasDirty = isDirty;
93626         }
93627     },
93628
93629     /**
93630      * @private Called when the field's dirty state changes.
93631      * @param {Boolean} isDirty
93632      */
93633     onDirtyChange: Ext.emptyFn,
93634
93635     /**
93636      * Runs this field's validators and returns an array of error messages for any validation failures. This is called
93637      * internally during validation and would not usually need to be used manually.
93638      *
93639      * Each subclass should override or augment the return value to provide their own errors.
93640      *
93641      * @param {Object} value The value to get errors for (defaults to the current field value)
93642      * @return {String[]} All error messages for this field; an empty Array if none.
93643      */
93644     getErrors: function(value) {
93645         return [];
93646     },
93647
93648     /**
93649      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
93650      * value. The {@link #validitychange} event will not be fired; use {@link #validate} instead if you want the event
93651      * to fire. **Note**: {@link #disabled} fields are always treated as valid.
93652      *
93653      * Implementations are encouraged to ensure that this method does not have side-effects such as triggering error
93654      * message display.
93655      *
93656      * @return {Boolean} True if the value is valid, else false
93657      */
93658     isValid : function() {
93659         var me = this;
93660         return me.disabled || Ext.isEmpty(me.getErrors());
93661     },
93662
93663     /**
93664      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the field's current
93665      * value, and fires the {@link #validitychange} event if the field's validity has changed since the last validation.
93666      * **Note**: {@link #disabled} fields are always treated as valid.
93667      *
93668      * Custom implementations of this method are allowed to have side-effects such as triggering error message display.
93669      * To validate without side-effects, use {@link #isValid}.
93670      *
93671      * @return {Boolean} True if the value is valid, else false
93672      */
93673     validate : function() {
93674         var me = this,
93675             isValid = me.isValid();
93676         if (isValid !== me.wasValid) {
93677             me.wasValid = isValid;
93678             me.fireEvent('validitychange', me, isValid);
93679         }
93680         return isValid;
93681     },
93682
93683     /**
93684      * A utility for grouping a set of modifications which may trigger value changes into a single transaction, to
93685      * prevent excessive firing of {@link #change} events. This is useful for instance if the field has sub-fields which
93686      * are being updated as a group; you don't want the container field to check its own changed state for each subfield
93687      * change.
93688      * @param {Object} fn A function containing the transaction code
93689      */
93690     batchChanges: function(fn) {
93691         try {
93692             this.suspendCheckChange++;
93693             fn();
93694         } catch(e){
93695             throw e;
93696         } finally {
93697             this.suspendCheckChange--;
93698         }
93699         this.checkChange();
93700     },
93701
93702     /**
93703      * Returns whether this Field is a file upload field; if it returns true, forms will use special techniques for
93704      * {@link Ext.form.Basic#submit submitting the form} via AJAX. See {@link Ext.form.Basic#hasUpload} for details. If
93705      * this returns true, the {@link #extractFileInput} method must also be implemented to return the corresponding file
93706      * input element.
93707      * @return {Boolean}
93708      */
93709     isFileUpload: function() {
93710         return false;
93711     },
93712
93713     /**
93714      * Only relevant if the instance's {@link #isFileUpload} method returns true. Returns a reference to the file input
93715      * DOM element holding the user's selected file. The input will be appended into the submission form and will not be
93716      * returned, so this method should also create a replacement.
93717      * @return {HTMLElement}
93718      */
93719     extractFileInput: function() {
93720         return null;
93721     },
93722
93723     /**
93724      * @method markInvalid
93725      * Associate one or more error messages with this field. Components using this mixin should implement this method to
93726      * update the component's rendering to display the messages.
93727      *
93728      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
93729      * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
93730      * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
93731      *
93732      * @param {String/String[]} errors The error message(s) for the field.
93733      */
93734     markInvalid: Ext.emptyFn,
93735
93736     /**
93737      * @method clearInvalid
93738      * Clear any invalid styles/messages for this field. Components using this mixin should implement this method to
93739      * update the components rendering to clear any existing messages.
93740      *
93741      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
93742      * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
93743      * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
93744      */
93745     clearInvalid: Ext.emptyFn
93746
93747 });
93748
93749 /**
93750  * @class Ext.layout.component.field.Field
93751  * @extends Ext.layout.component.Component
93752  * Layout class for components with {@link Ext.form.Labelable field labeling}, handling the sizing and alignment of
93753  * the form control, label, and error message treatment.
93754  * @private
93755  */
93756 Ext.define('Ext.layout.component.field.Field', {
93757
93758     /* Begin Definitions */
93759
93760     alias: ['layout.field'],
93761
93762     extend: 'Ext.layout.component.Component',
93763
93764     uses: ['Ext.tip.QuickTip', 'Ext.util.TextMetrics'],
93765
93766     /* End Definitions */
93767
93768     type: 'field',
93769
93770     beforeLayout: function(width, height) {
93771         var me = this;
93772         return me.callParent(arguments) || (!me.owner.preventMark && me.activeError !== me.owner.getActiveError());
93773     },
93774
93775     onLayout: function(width, height) {
93776         var me = this,
93777             owner = me.owner,
93778             labelStrategy = me.getLabelStrategy(),
93779             errorStrategy = me.getErrorStrategy(),
93780             isDefined = Ext.isDefined,
93781             isNumber = Ext.isNumber,
93782             lastSize, autoWidth, autoHeight, info, undef;
93783
93784         lastSize = me.lastComponentSize || {};
93785         if (!isDefined(width)) {
93786             width = lastSize.width;
93787             if (width < 0) { //first pass lastComponentSize.width is -Infinity
93788                 width = undef;
93789             }
93790         }
93791         if (!isDefined(height)) {
93792             height = lastSize.height;
93793             if (height < 0) { //first pass lastComponentSize.height is -Infinity
93794                 height = undef;
93795             }
93796         }
93797         autoWidth = !isNumber(width);
93798         autoHeight = !isNumber(height);
93799
93800         info = {
93801             autoWidth: autoWidth,
93802             autoHeight: autoHeight,
93803             width: autoWidth ? owner.getBodyNaturalWidth() : width, //always give a pixel width
93804             height: height,
93805             setOuterWidth: false, //whether the outer el width should be set to the calculated width
93806
93807             // insets for the bodyEl from each side of the component layout area
93808             insets: {
93809                 top: 0,
93810                 right: 0,
93811                 bottom: 0,
93812                 left: 0
93813             }
93814         };
93815
93816         // NOTE the order of calculating insets and setting styles here is very important; we must first
93817         // calculate and set horizontal layout alone, as the horizontal sizing of elements can have an impact
93818         // on the vertical sizes due to wrapping, then calculate and set the vertical layout.
93819
93820         // perform preparation on the label and error (setting css classes, qtips, etc.)
93821         labelStrategy.prepare(owner, info);
93822         errorStrategy.prepare(owner, info);
93823
93824         // calculate the horizontal insets for the label and error
93825         labelStrategy.adjustHorizInsets(owner, info);
93826         errorStrategy.adjustHorizInsets(owner, info);
93827
93828         // set horizontal styles for label and error based on the current insets
93829         labelStrategy.layoutHoriz(owner, info);
93830         errorStrategy.layoutHoriz(owner, info);
93831
93832         // calculate the vertical insets for the label and error
93833         labelStrategy.adjustVertInsets(owner, info);
93834         errorStrategy.adjustVertInsets(owner, info);
93835
93836         // set vertical styles for label and error based on the current insets
93837         labelStrategy.layoutVert(owner, info);
93838         errorStrategy.layoutVert(owner, info);
93839
93840         // perform sizing of the elements based on the final dimensions and insets
93841         if (autoWidth && autoHeight) {
93842             // Don't use setTargetSize if auto-sized, so the calculated size is not reused next time
93843             me.setElementSize(owner.el, (info.setOuterWidth ? info.width : undef), info.height);
93844         } else {
93845             me.setTargetSize((!autoWidth || info.setOuterWidth ? info.width : undef), info.height);
93846         }
93847         me.sizeBody(info);
93848
93849         me.activeError = owner.getActiveError();
93850     },
93851     
93852     onFocus: function(){
93853         this.getErrorStrategy().onFocus(this.owner);    
93854     },
93855
93856
93857     /**
93858      * Perform sizing and alignment of the bodyEl (and children) to match the calculated insets.
93859      */
93860     sizeBody: function(info) {
93861         var me = this,
93862             owner = me.owner,
93863             insets = info.insets,
93864             totalWidth = info.width,
93865             totalHeight = info.height,
93866             width = Ext.isNumber(totalWidth) ? totalWidth - insets.left - insets.right : totalWidth,
93867             height = Ext.isNumber(totalHeight) ? totalHeight - insets.top - insets.bottom : totalHeight;
93868
93869         // size the bodyEl
93870         me.setElementSize(owner.bodyEl, width, height);
93871
93872         // size the bodyEl's inner contents if necessary
93873         me.sizeBodyContents(width, height);
93874     },
93875
93876     /**
93877      * Size the contents of the field body, given the full dimensions of the bodyEl. Does nothing by
93878      * default, subclasses can override to handle their specific contents.
93879      * @param {Number} width The bodyEl width
93880      * @param {Number} height The bodyEl height
93881      */
93882     sizeBodyContents: Ext.emptyFn,
93883
93884
93885     /**
93886      * Return the set of strategy functions from the {@link #labelStrategies labelStrategies collection}
93887      * that is appropriate for the field's {@link Ext.form.Labelable#labelAlign labelAlign} config.
93888      */
93889     getLabelStrategy: function() {
93890         var me = this,
93891             strategies = me.labelStrategies,
93892             labelAlign = me.owner.labelAlign;
93893         return strategies[labelAlign] || strategies.base;
93894     },
93895
93896     /**
93897      * Return the set of strategy functions from the {@link #errorStrategies errorStrategies collection}
93898      * that is appropriate for the field's {@link Ext.form.Labelable#msgTarget msgTarget} config.
93899      */
93900     getErrorStrategy: function() {
93901         var me = this,
93902             owner = me.owner,
93903             strategies = me.errorStrategies,
93904             msgTarget = owner.msgTarget;
93905         return !owner.preventMark && Ext.isString(msgTarget) ?
93906                 (strategies[msgTarget] || strategies.elementId) :
93907                 strategies.none;
93908     },
93909
93910
93911
93912     /**
93913      * Collection of named strategies for laying out and adjusting labels to accommodate error messages.
93914      * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#labelAlign} config.
93915      */
93916     labelStrategies: (function() {
93917         var applyIf = Ext.applyIf,
93918             emptyFn = Ext.emptyFn,
93919             base = {
93920                 prepare: function(owner, info) {
93921                     var cls = owner.labelCls + '-' + owner.labelAlign,
93922                         labelEl = owner.labelEl;
93923                     if (labelEl && !labelEl.hasCls(cls)) {
93924                         labelEl.addCls(cls);
93925                     }
93926                 },
93927                 adjustHorizInsets: emptyFn,
93928                 adjustVertInsets: emptyFn,
93929                 layoutHoriz: emptyFn,
93930                 layoutVert: emptyFn
93931             },
93932             left = applyIf({
93933                 prepare: function(owner, info) {
93934                     base.prepare(owner, info);
93935                     // If auto width, add the label width to the body's natural width.
93936                     if (info.autoWidth) {
93937                         info.width += (!owner.labelEl ? 0 : owner.labelWidth + owner.labelPad);
93938                     }
93939                     // Must set outer width to prevent field from wrapping below floated label
93940                     info.setOuterWidth = true;
93941                 },
93942                 adjustHorizInsets: function(owner, info) {
93943                     if (owner.labelEl) {
93944                         info.insets.left += owner.labelWidth + owner.labelPad;
93945                     }
93946                 },
93947                 layoutHoriz: function(owner, info) {
93948                     // For content-box browsers we can't rely on Labelable.js#getLabelableRenderData
93949                     // setting the width style because it needs to account for the final calculated
93950                     // padding/border styles for the label. So we set the width programmatically here to
93951                     // normalize content-box sizing, while letting border-box browsers use the original
93952                     // width style.
93953                     var labelEl = owner.labelEl;
93954                     if (labelEl && !owner.isLabelSized && !Ext.isBorderBox) {
93955                         labelEl.setWidth(owner.labelWidth);
93956                         owner.isLabelSized = true;
93957                     }
93958                 }
93959             }, base);
93960
93961
93962         return {
93963             base: base,
93964
93965             /**
93966              * Label displayed above the bodyEl
93967              */
93968             top: applyIf({
93969                 adjustVertInsets: function(owner, info) {
93970                     var labelEl = owner.labelEl;
93971                     if (labelEl) {
93972                         info.insets.top += Ext.util.TextMetrics.measure(labelEl, owner.fieldLabel, info.width).height +
93973                                            labelEl.getFrameWidth('tb') + owner.labelPad;
93974                     }
93975                 }
93976             }, base),
93977
93978             /**
93979              * Label displayed to the left of the bodyEl
93980              */
93981             left: left,
93982
93983             /**
93984              * Same as left, only difference is text-align in CSS
93985              */
93986             right: left
93987         };
93988     })(),
93989
93990
93991
93992     /**
93993      * Collection of named strategies for laying out and adjusting insets to accommodate error messages.
93994      * An appropriate one will be chosen based on the owner field's {@link Ext.form.Labelable#msgTarget} config.
93995      */
93996     errorStrategies: (function() {
93997         function setDisplayed(el, displayed) {
93998             var wasDisplayed = el.getStyle('display') !== 'none';
93999             if (displayed !== wasDisplayed) {
94000                 el.setDisplayed(displayed);
94001             }
94002         }
94003
94004         function setStyle(el, name, value) {
94005             if (el.getStyle(name) !== value) {
94006                 el.setStyle(name, value);
94007             }
94008         }
94009         
94010         function showTip(owner) {
94011             var tip = Ext.layout.component.field.Field.tip,
94012                 target;
94013                 
94014             if (tip && tip.isVisible()) {
94015                 target = tip.activeTarget;
94016                 if (target && target.el === owner.getActionEl().dom) {
94017                     tip.toFront(true);
94018                 }
94019             }
94020         }
94021
94022         var applyIf = Ext.applyIf,
94023             emptyFn = Ext.emptyFn,
94024             base = {
94025                 prepare: function(owner) {
94026                     setDisplayed(owner.errorEl, false);
94027                 },
94028                 adjustHorizInsets: emptyFn,
94029                 adjustVertInsets: emptyFn,
94030                 layoutHoriz: emptyFn,
94031                 layoutVert: emptyFn,
94032                 onFocus: emptyFn
94033             };
94034
94035         return {
94036             none: base,
94037
94038             /**
94039              * Error displayed as icon (with QuickTip on hover) to right of the bodyEl
94040              */
94041             side: applyIf({
94042                 prepare: function(owner) {
94043                     var errorEl = owner.errorEl;
94044                     errorEl.addCls(Ext.baseCSSPrefix + 'form-invalid-icon');
94045                     Ext.layout.component.field.Field.initTip();
94046                     errorEl.dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
94047                     setDisplayed(errorEl, owner.hasActiveError());
94048                 },
94049                 adjustHorizInsets: function(owner, info) {
94050                     if (owner.autoFitErrors && owner.hasActiveError()) {
94051                         info.insets.right += owner.errorEl.getWidth();
94052                     }
94053                 },
94054                 layoutHoriz: function(owner, info) {
94055                     if (owner.hasActiveError()) {
94056                         setStyle(owner.errorEl, 'left', info.width - info.insets.right + 'px');
94057                     }
94058                 },
94059                 layoutVert: function(owner, info) {
94060                     if (owner.hasActiveError()) {
94061                         setStyle(owner.errorEl, 'top', info.insets.top + 'px');
94062                     }
94063                 },
94064                 onFocus: showTip
94065             }, base),
94066
94067             /**
94068              * Error message displayed underneath the bodyEl
94069              */
94070             under: applyIf({
94071                 prepare: function(owner) {
94072                     var errorEl = owner.errorEl,
94073                         cls = Ext.baseCSSPrefix + 'form-invalid-under';
94074                     if (!errorEl.hasCls(cls)) {
94075                         errorEl.addCls(cls);
94076                     }
94077                     setDisplayed(errorEl, owner.hasActiveError());
94078                 },
94079                 adjustVertInsets: function(owner, info) {
94080                     if (owner.autoFitErrors) {
94081                         info.insets.bottom += owner.errorEl.getHeight();
94082                     }
94083                 },
94084                 layoutHoriz: function(owner, info) {
94085                     var errorEl = owner.errorEl,
94086                         insets = info.insets;
94087
94088                     setStyle(errorEl, 'width', info.width - insets.right - insets.left + 'px');
94089                     setStyle(errorEl, 'marginLeft', insets.left + 'px');
94090                 }
94091             }, base),
94092
94093             /**
94094              * Error displayed as QuickTip on hover of the field container
94095              */
94096             qtip: applyIf({
94097                 prepare: function(owner) {
94098                     setDisplayed(owner.errorEl, false);
94099                     Ext.layout.component.field.Field.initTip();
94100                     owner.getActionEl().dom.setAttribute('data-errorqtip', owner.getActiveError() || '');
94101                 },
94102                 onFocus: showTip
94103             }, base),
94104
94105             /**
94106              * Error displayed as title tip on hover of the field container
94107              */
94108             title: applyIf({
94109                 prepare: function(owner) {
94110                     setDisplayed(owner.errorEl, false);
94111                     owner.el.dom.title = owner.getActiveError() || '';
94112                 }
94113             }, base),
94114
94115             /**
94116              * Error message displayed as content of an element with a given id elsewhere in the app
94117              */
94118             elementId: applyIf({
94119                 prepare: function(owner) {
94120                     setDisplayed(owner.errorEl, false);
94121                     var targetEl = Ext.fly(owner.msgTarget);
94122                     if (targetEl) {
94123                         targetEl.dom.innerHTML = owner.getActiveError() || '';
94124                         targetEl.setDisplayed(owner.hasActiveError());
94125                     }
94126                 }
94127             }, base)
94128         };
94129     })(),
94130
94131     statics: {
94132         /**
94133          * Use a custom QuickTip instance separate from the main QuickTips singleton, so that we
94134          * can give it a custom frame style. Responds to errorqtip rather than the qtip property.
94135          */
94136         initTip: function() {
94137             var tip = this.tip;
94138             if (!tip) {
94139                 tip = this.tip = Ext.create('Ext.tip.QuickTip', {
94140                     baseCls: Ext.baseCSSPrefix + 'form-invalid-tip',
94141                     renderTo: Ext.getBody()
94142                 });
94143                 tip.tagConfig = Ext.apply({}, {attribute: 'errorqtip'}, tip.tagConfig);
94144             }
94145         },
94146
94147         /**
94148          * Destroy the error tip instance.
94149          */
94150         destroyTip: function() {
94151             var tip = this.tip;
94152             if (tip) {
94153                 tip.destroy();
94154                 delete this.tip;
94155             }
94156         }
94157     }
94158
94159 });
94160
94161 /**
94162  * @singleton
94163  * @alternateClassName Ext.form.VTypes
94164  *
94165  * This is a singleton object which contains a set of commonly used field validation functions
94166  * and provides a mechanism for creating reusable custom field validations.
94167  * The following field validation functions are provided out of the box:
94168  *
94169  * - {@link #alpha}
94170  * - {@link #alphanum}
94171  * - {@link #email}
94172  * - {@link #url}
94173  *
94174  * VTypes can be applied to a {@link Ext.form.field.Text Text Field} using the `{@link Ext.form.field.Text#vtype vtype}` configuration:
94175  *
94176  *     Ext.create('Ext.form.field.Text', {
94177  *         fieldLabel: 'Email Address',
94178  *         name: 'email',
94179  *         vtype: 'email' // applies email validation rules to this field
94180  *     });
94181  *
94182  * To create custom VTypes:
94183  *
94184  *     // custom Vtype for vtype:'time'
94185  *     var timeTest = /^([1-9]|1[0-9]):([0-5][0-9])(\s[a|p]m)$/i;
94186  *     Ext.apply(Ext.form.field.VTypes, {
94187  *         //  vtype validation function
94188  *         time: function(val, field) {
94189  *             return timeTest.test(val);
94190  *         },
94191  *         // vtype Text property: The error text to display when the validation function returns false
94192  *         timeText: 'Not a valid time.  Must be in the format "12:34 PM".',
94193  *         // vtype Mask property: The keystroke filter mask
94194  *         timeMask: /[\d\s:amp]/i
94195  *     });
94196  *
94197  * In the above example the `time` function is the validator that will run when field validation occurs,
94198  * `timeText` is the error message, and `timeMask` limits what characters can be typed into the field.
94199  * Note that the `Text` and `Mask` functions must begin with the same name as the validator function.
94200  *
94201  * Using a custom validator is the same as using one of the build-in validators - just use the name of the validator function
94202  * as the `{@link Ext.form.field.Text#vtype vtype}` configuration on a {@link Ext.form.field.Text Text Field}:
94203  *
94204  *     Ext.create('Ext.form.field.Text', {
94205  *         fieldLabel: 'Departure Time',
94206  *         name: 'departureTime',
94207  *         vtype: 'time' // applies custom time validation rules to this field
94208  *     });
94209  *
94210  * Another example of a custom validator:
94211  *
94212  *     // custom Vtype for vtype:'IPAddress'
94213  *     Ext.apply(Ext.form.field.VTypes, {
94214  *         IPAddress:  function(v) {
94215  *             return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(v);
94216  *         },
94217  *         IPAddressText: 'Must be a numeric IP address',
94218  *         IPAddressMask: /[\d\.]/i
94219  *     });
94220  *
94221  * It's important to note that using {@link Ext#apply Ext.apply()} means that the custom validator function
94222  * as well as `Text` and `Mask` fields are added as properties of the `Ext.form.field.VTypes` singleton.
94223  */
94224 Ext.define('Ext.form.field.VTypes', (function(){
94225     // closure these in so they are only created once.
94226     var alpha = /^[a-zA-Z_]+$/,
94227         alphanum = /^[a-zA-Z0-9_]+$/,
94228         email = /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/,
94229         url = /(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
94230
94231     // All these messages and functions are configurable
94232     return {
94233         singleton: true,
94234         alternateClassName: 'Ext.form.VTypes',
94235
94236         /**
94237          * The function used to validate email addresses. Note that this is a very basic validation - complete
94238          * validation per the email RFC specifications is very complex and beyond the scope of this class, although this
94239          * function can be overridden if a more comprehensive validation scheme is desired. See the validation section
94240          * of the [Wikipedia article on email addresses][1] for additional information. This implementation is intended
94241          * to validate the following emails:
94242          *
94243          * - `barney@example.de`
94244          * - `barney.rubble@example.com`
94245          * - `barney-rubble@example.coop`
94246          * - `barney+rubble@example.com`
94247          *
94248          * [1]: http://en.wikipedia.org/wiki/E-mail_address
94249          *
94250          * @param {String} value The email address
94251          * @return {Boolean} true if the RegExp test passed, and false if not.
94252          */
94253         'email' : function(v){
94254             return email.test(v);
94255         },
94256         /**
94257          * @property {String} emailText
94258          * The error text to display when the email validation function returns false.
94259          * Defaults to: 'This field should be an e-mail address in the format "user@example.com"'
94260          */
94261         'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
94262         /**
94263          * @property {RegExp} emailMask
94264          * The keystroke filter mask to be applied on email input. See the {@link #email} method for information about
94265          * more complex email validation. Defaults to: /[a-z0-9_\.\-@]/i
94266          */
94267         'emailMask' : /[a-z0-9_\.\-@\+]/i,
94268
94269         /**
94270          * The function used to validate URLs
94271          * @param {String} value The URL
94272          * @return {Boolean} true if the RegExp test passed, and false if not.
94273          */
94274         'url' : function(v){
94275             return url.test(v);
94276         },
94277         /**
94278          * @property {String} urlText
94279          * The error text to display when the url validation function returns false.
94280          * Defaults to: 'This field should be a URL in the format "http:/'+'/www.example.com"'
94281          */
94282         'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
94283
94284         /**
94285          * The function used to validate alpha values
94286          * @param {String} value The value
94287          * @return {Boolean} true if the RegExp test passed, and false if not.
94288          */
94289         'alpha' : function(v){
94290             return alpha.test(v);
94291         },
94292         /**
94293          * @property {String} alphaText
94294          * The error text to display when the alpha validation function returns false.
94295          * Defaults to: 'This field should only contain letters and _'
94296          */
94297         'alphaText' : 'This field should only contain letters and _',
94298         /**
94299          * @property {RegExp} alphaMask
94300          * The keystroke filter mask to be applied on alpha input. Defaults to: /[a-z_]/i
94301          */
94302         'alphaMask' : /[a-z_]/i,
94303
94304         /**
94305          * The function used to validate alphanumeric values
94306          * @param {String} value The value
94307          * @return {Boolean} true if the RegExp test passed, and false if not.
94308          */
94309         'alphanum' : function(v){
94310             return alphanum.test(v);
94311         },
94312         /**
94313          * @property {String} alphanumText
94314          * The error text to display when the alphanumeric validation function returns false.
94315          * Defaults to: 'This field should only contain letters, numbers and _'
94316          */
94317         'alphanumText' : 'This field should only contain letters, numbers and _',
94318         /**
94319          * @property {RegExp} alphanumMask
94320          * The keystroke filter mask to be applied on alphanumeric input. Defaults to: /[a-z0-9_]/i
94321          */
94322         'alphanumMask' : /[a-z0-9_]/i
94323     };
94324 })());
94325
94326 /**
94327  * @private
94328  * @class Ext.layout.component.field.Text
94329  * @extends Ext.layout.component.field.Field
94330  * Layout class for {@link Ext.form.field.Text} fields. Handles sizing the input field.
94331  */
94332 Ext.define('Ext.layout.component.field.Text', {
94333     extend: 'Ext.layout.component.field.Field',
94334     alias: 'layout.textfield',
94335     requires: ['Ext.util.TextMetrics'],
94336
94337     type: 'textfield',
94338
94339
94340     /**
94341      * Allow layout to proceed if the {@link Ext.form.field.Text#grow} config is enabled and the value has
94342      * changed since the last layout.
94343      */
94344     beforeLayout: function(width, height) {
94345         var me = this,
94346             owner = me.owner,
94347             lastValue = this.lastValue,
94348             value = owner.getRawValue();
94349         this.lastValue = value;
94350         return me.callParent(arguments) || (owner.grow && value !== lastValue);
94351     },
94352
94353
94354     /**
94355      * Size the field body contents given the total dimensions of the bodyEl, taking into account the optional
94356      * {@link Ext.form.field.Text#grow} configurations.
94357      * @param {Number} width The bodyEl width
94358      * @param {Number} height The bodyEl height
94359      */
94360     sizeBodyContents: function(width, height) {
94361         var size = this.adjustForGrow(width, height);
94362         this.setElementSize(this.owner.inputEl, size[0], size[1]);
94363     },
94364
94365
94366     /**
94367      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
94368      * size based on the text field's {@link Ext.form.field.Text#grow grow config}.
94369      * @param {Number} width The bodyEl width
94370      * @param {Number} height The bodyEl height
94371      * @return {Number[]} [inputElWidth, inputElHeight]
94372      */
94373     adjustForGrow: function(width, height) {
94374         var me = this,
94375             owner = me.owner,
94376             inputEl, value, calcWidth,
94377             result = [width, height];
94378
94379         if (owner.grow) {
94380             inputEl = owner.inputEl;
94381
94382             // Find the width that contains the whole text value
94383             value = (inputEl.dom.value || (owner.hasFocus ? '' : owner.emptyText) || '') + owner.growAppend;
94384             calcWidth = inputEl.getTextWidth(value) + inputEl.getBorderWidth("lr") + inputEl.getPadding("lr");
94385
94386             // Constrain
94387             result[0] = Ext.Number.constrain(calcWidth, owner.growMin,
94388                     Math.max(owner.growMin, Math.min(owner.growMax, Ext.isNumber(width) ? width : Infinity)));
94389         }
94390
94391         return result;
94392     }
94393
94394 });
94395
94396 /**
94397  * @private
94398  * @class Ext.layout.component.field.TextArea
94399  * @extends Ext.layout.component.field.Field
94400  * Layout class for {@link Ext.form.field.TextArea} fields. Handles sizing the textarea field.
94401  */
94402 Ext.define('Ext.layout.component.field.TextArea', {
94403     extend: 'Ext.layout.component.field.Text',
94404     alias: 'layout.textareafield',
94405
94406     type: 'textareafield',
94407
94408
94409     /**
94410      * Given the target bodyEl dimensions, adjust them if necessary to return the correct final
94411      * size based on the text field's {@link Ext.form.field.Text#grow grow config}. Overrides the
94412      * textfield layout's implementation to handle height rather than width.
94413      * @param {Number} width The bodyEl width
94414      * @param {Number} height The bodyEl height
94415      * @return {Number[]} [inputElWidth, inputElHeight]
94416      */
94417     adjustForGrow: function(width, height) {
94418         var me = this,
94419             owner = me.owner,
94420             inputEl, value, max,
94421             curWidth, curHeight, calcHeight,
94422             result = [width, height];
94423
94424         if (owner.grow) {
94425             inputEl = owner.inputEl;
94426             curWidth = inputEl.getWidth(true); //subtract border/padding to get the available width for the text
94427             curHeight = inputEl.getHeight();
94428
94429             // Get and normalize the field value for measurement
94430             value = inputEl.dom.value || '&#160;';
94431             value += owner.growAppend;
94432
94433             // Translate newlines to <br> tags
94434             value = value.replace(/\n/g, '<br>');
94435
94436             // Find the height that contains the whole text value
94437             calcHeight = Ext.util.TextMetrics.measure(inputEl, value, curWidth).height +
94438                          inputEl.getBorderWidth("tb") + inputEl.getPadding("tb");
94439
94440             // Constrain
94441             max = owner.growMax;
94442             if (Ext.isNumber(height)) {
94443                 max = Math.min(max, height);
94444             }
94445             result[1] = Ext.Number.constrain(calcHeight, owner.growMin, max);
94446         }
94447
94448         return result;
94449     }
94450
94451 });
94452 /**
94453  * @class Ext.layout.container.Anchor
94454  * @extends Ext.layout.container.Container
94455  * 
94456  * This is a layout that enables anchoring of contained elements relative to the container's dimensions.
94457  * If the container is resized, all anchored items are automatically rerendered according to their
94458  * `{@link #anchor}` rules.
94459  *
94460  * This class is intended to be extended or created via the {@link Ext.container.AbstractContainer#layout layout}: 'anchor' 
94461  * config, and should generally not need to be created directly via the new keyword.
94462  * 
94463  * AnchorLayout does not have any direct config options (other than inherited ones). By default,
94464  * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
94465  * container using the AnchorLayout can supply an anchoring-specific config property of `anchorSize`.
94466  *
94467  * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
94468  * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
94469  * logic if necessary.  
94470  *
94471  *     @example
94472  *     Ext.create('Ext.Panel', {
94473  *         width: 500,
94474  *         height: 400,
94475  *         title: "AnchorLayout Panel",
94476  *         layout: 'anchor',
94477  *         renderTo: Ext.getBody(),
94478  *         items: [
94479  *             {
94480  *                 xtype: 'panel',
94481  *                 title: '75% Width and 20% Height',
94482  *                 anchor: '75% 20%'
94483  *             },
94484  *             {
94485  *                 xtype: 'panel',
94486  *                 title: 'Offset -300 Width & -200 Height',
94487  *                 anchor: '-300 -200'          
94488  *             },
94489  *             {
94490  *                 xtype: 'panel',
94491  *                 title: 'Mixed Offset and Percent',
94492  *                 anchor: '-250 20%'
94493  *             }
94494  *         ]
94495  *     });
94496  */
94497 Ext.define('Ext.layout.container.Anchor', {
94498
94499     /* Begin Definitions */
94500
94501     alias: 'layout.anchor',
94502     extend: 'Ext.layout.container.Container',
94503     alternateClassName: 'Ext.layout.AnchorLayout',
94504
94505     /* End Definitions */
94506
94507     /**
94508      * @cfg {String} anchor
94509      *
94510      * This configuation option is to be applied to **child `items`** of a container managed by
94511      * this layout (ie. configured with `layout:'anchor'`).
94512      *
94513      * This value is what tells the layout how an item should be anchored to the container. `items`
94514      * added to an AnchorLayout accept an anchoring-specific config property of **anchor** which is a string
94515      * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
94516      * The following types of anchor values are supported:
94517      *
94518      * - **Percentage** : Any value between 1 and 100, expressed as a percentage.
94519      *
94520      *   The first anchor is the percentage width that the item should take up within the container, and the
94521      *   second is the percentage height.  For example:
94522      *
94523      *       // two values specified
94524      *       anchor: '100% 50%' // render item complete width of the container and
94525      *                          // 1/2 height of the container
94526      *       // one value specified
94527      *       anchor: '100%'     // the width value; the height will default to auto
94528      *
94529      * - **Offsets** : Any positive or negative integer value.
94530      *
94531      *   This is a raw adjustment where the first anchor is the offset from the right edge of the container,
94532      *   and the second is the offset from the bottom edge. For example:
94533      *
94534      *       // two values specified
94535      *       anchor: '-50 -100' // render item the complete width of the container
94536      *                          // minus 50 pixels and
94537      *                          // the complete height minus 100 pixels.
94538      *       // one value specified
94539      *       anchor: '-50'      // anchor value is assumed to be the right offset value
94540      *                          // bottom offset will default to 0
94541      *
94542      * - **Sides** : Valid values are `right` (or `r`) and `bottom` (or `b`).
94543      *
94544      *   Either the container must have a fixed size or an anchorSize config value defined at render time in
94545      *   order for these to have any effect.
94546      *   
94547      * - **Mixed** :
94548      *
94549      *   Anchor values can also be mixed as needed.  For example, to render the width offset from the container
94550      *   right edge by 50 pixels and 75% of the container's height use:
94551      *   
94552      *       anchor:   '-50 75%'
94553      */
94554     type: 'anchor',
94555
94556     /**
94557      * @cfg {String} defaultAnchor
94558      * 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%'.
94559      */
94560     defaultAnchor: '100%',
94561
94562     parseAnchorRE: /^(r|right|b|bottom)$/i,
94563
94564     // private
94565     onLayout: function() {
94566         this.callParent(arguments);
94567
94568         var me = this,
94569             size = me.getLayoutTargetSize(),
94570             owner = me.owner,
94571             target = me.getTarget(),
94572             ownerWidth = size.width,
94573             ownerHeight = size.height,
94574             overflow = target.getStyle('overflow'),
94575             components = me.getVisibleItems(owner),
94576             len = components.length,
94577             boxes = [],
94578             box, newTargetSize, component, anchorSpec, calcWidth, calcHeight,
94579             i, el, cleaner;
94580
94581         if (ownerWidth < 20 && ownerHeight < 20) {
94582             return;
94583         }
94584
94585         // Anchor layout uses natural HTML flow to arrange the child items.
94586         // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
94587         // containing element height, we create a zero-sized element with style clear:both to force a "new line"
94588         if (!me.clearEl) {
94589             me.clearEl = target.createChild({
94590                 cls: Ext.baseCSSPrefix + 'clear',
94591                 role: 'presentation'
94592             });
94593         }
94594
94595         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
94596         if (!Ext.supports.RightMargin) {
94597             cleaner = Ext.Element.getRightMarginFixCleaner(target);
94598             target.addCls(Ext.baseCSSPrefix + 'inline-children');
94599         }
94600
94601         for (i = 0; i < len; i++) {
94602             component = components[i];
94603             el = component.el;
94604
94605             anchorSpec = component.anchorSpec;
94606             if (anchorSpec) {
94607                 if (anchorSpec.right) {
94608                     calcWidth = me.adjustWidthAnchor(anchorSpec.right(ownerWidth) - el.getMargin('lr'), component);
94609                 } else {
94610                     calcWidth = undefined;
94611                 }
94612                 if (anchorSpec.bottom) {
94613                     calcHeight = me.adjustHeightAnchor(anchorSpec.bottom(ownerHeight) - el.getMargin('tb'), component);
94614                 } else {
94615                     calcHeight = undefined;
94616                 }
94617
94618                 boxes.push({
94619                     component: component,
94620                     anchor: true,
94621                     width: calcWidth || undefined,
94622                     height: calcHeight || undefined
94623                 });
94624             } else {
94625                 boxes.push({
94626                     component: component,
94627                     anchor: false
94628                 });
94629             }
94630         }
94631
94632         // Work around WebKit RightMargin bug. We're going to inline-block all the children only ONCE and remove it when we're done
94633         if (!Ext.supports.RightMargin) {
94634             target.removeCls(Ext.baseCSSPrefix + 'inline-children');
94635             cleaner();
94636         }
94637
94638         for (i = 0; i < len; i++) {
94639             box = boxes[i];
94640             me.setItemSize(box.component, box.width, box.height);
94641         }
94642
94643         if (overflow && overflow != 'hidden' && !me.adjustmentPass) {
94644             newTargetSize = me.getLayoutTargetSize();
94645             if (newTargetSize.width != size.width || newTargetSize.height != size.height) {
94646                 me.adjustmentPass = true;
94647                 me.onLayout();
94648             }
94649         }
94650
94651         delete me.adjustmentPass;
94652     },
94653
94654     // private
94655     parseAnchor: function(a, start, cstart) {
94656         if (a && a != 'none') {
94657             var ratio;
94658             // standard anchor
94659             if (this.parseAnchorRE.test(a)) {
94660                 var diff = cstart - start;
94661                 return function(v) {
94662                     return v - diff;
94663                 };
94664             }    
94665             // percentage
94666             else if (a.indexOf('%') != -1) {
94667                 ratio = parseFloat(a.replace('%', '')) * 0.01;
94668                 return function(v) {
94669                     return Math.floor(v * ratio);
94670                 };
94671             }    
94672             // simple offset adjustment
94673             else {
94674                 a = parseInt(a, 10);
94675                 if (!isNaN(a)) {
94676                     return function(v) {
94677                         return v + a;
94678                     };
94679                 }
94680             }
94681         }
94682         return null;
94683     },
94684
94685     // private
94686     adjustWidthAnchor: function(value, comp) {
94687         return value;
94688     },
94689
94690     // private
94691     adjustHeightAnchor: function(value, comp) {
94692         return value;
94693     },
94694
94695     configureItem: function(item) {
94696         var me = this,
94697             owner = me.owner,
94698             anchor= item.anchor,
94699             anchorsArray,
94700             anchorSpec,
94701             anchorWidth,
94702             anchorHeight;
94703
94704         if (!item.anchor && item.items && !Ext.isNumber(item.width) && !(Ext.isIE6 && Ext.isStrict)) {
94705             item.anchor = anchor = me.defaultAnchor;
94706         }
94707
94708         // find the container anchoring size
94709         if (owner.anchorSize) {
94710             if (typeof owner.anchorSize == 'number') {
94711                 anchorWidth = owner.anchorSize;
94712             }
94713             else {
94714                 anchorWidth = owner.anchorSize.width;
94715                 anchorHeight = owner.anchorSize.height;
94716             }
94717         }
94718         else {
94719             anchorWidth = owner.initialConfig.width;
94720             anchorHeight = owner.initialConfig.height;
94721         }
94722
94723         if (anchor) {
94724             // cache all anchor values
94725             anchorsArray = anchor.split(' ');
94726             item.anchorSpec = anchorSpec = {
94727                 right: me.parseAnchor(anchorsArray[0], item.initialConfig.width, anchorWidth),
94728                 bottom: me.parseAnchor(anchorsArray[1], item.initialConfig.height, anchorHeight)
94729             };
94730
94731             if (anchorSpec.right) {
94732                 item.layoutManagedWidth = 1;
94733             } else {
94734                 item.layoutManagedWidth = 2;
94735             }
94736
94737             if (anchorSpec.bottom) {
94738                 item.layoutManagedHeight = 1;
94739             } else {
94740                 item.layoutManagedHeight = 2;
94741             }
94742         } else {
94743             item.layoutManagedWidth = 2;
94744             item.layoutManagedHeight = 2;
94745         }
94746         this.callParent(arguments);
94747     }
94748
94749 });
94750 /**
94751  * @class Ext.form.action.Load
94752  * @extends Ext.form.action.Action
94753  * <p>A class which handles loading of data from a server into the Fields of an {@link Ext.form.Basic}.</p>
94754  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
94755  * {@link Ext.form.Basic#load load}ing.</p>
94756  * <p><u><b>Response Packet Criteria</b></u></p>
94757  * <p>A response packet <b>must</b> contain:
94758  * <div class="mdetail-params"><ul>
94759  * <li><b><code>success</code></b> property : Boolean</li>
94760  * <li><b><code>data</code></b> property : Object</li>
94761  * <div class="sub-desc">The <code>data</code> property contains the values of Fields to load.
94762  * The individual value object for each Field is passed to the Field's
94763  * {@link Ext.form.field.Field#setValue setValue} method.</div></li>
94764  * </ul></div>
94765  * <p><u><b>JSON Packets</b></u></p>
94766  * <p>By default, response packets are assumed to be JSON, so for the following form load call:<pre><code>
94767 var myFormPanel = new Ext.form.Panel({
94768     title: 'Client and routing info',
94769     items: [{
94770         fieldLabel: 'Client',
94771         name: 'clientName'
94772     }, {
94773         fieldLabel: 'Port of loading',
94774         name: 'portOfLoading'
94775     }, {
94776         fieldLabel: 'Port of discharge',
94777         name: 'portOfDischarge'
94778     }]
94779 });
94780 myFormPanel.{@link Ext.form.Panel#getForm getForm}().{@link Ext.form.Basic#load load}({
94781     url: '/getRoutingInfo.php',
94782     params: {
94783         consignmentRef: myConsignmentRef
94784     },
94785     failure: function(form, action) {
94786         Ext.Msg.alert("Load failed", action.result.errorMessage);
94787     }
94788 });
94789 </code></pre>
94790  * a <b>success response</b> packet may look like this:</p><pre><code>
94791 {
94792     success: true,
94793     data: {
94794         clientName: "Fred. Olsen Lines",
94795         portOfLoading: "FXT",
94796         portOfDischarge: "OSL"
94797     }
94798 }</code></pre>
94799  * while a <b>failure response</b> packet may look like this:</p><pre><code>
94800 {
94801     success: false,
94802     errorMessage: "Consignment reference not found"
94803 }</code></pre>
94804  * <p>Other data may be placed into the response for processing the {@link Ext.form.Basic Form}'s
94805  * callback or event handler methods. The object decoded from this JSON is available in the
94806  * {@link Ext.form.action.Action#result result} property.</p>
94807  */
94808 Ext.define('Ext.form.action.Load', {
94809     extend:'Ext.form.action.Action',
94810     requires: ['Ext.data.Connection'],
94811     alternateClassName: 'Ext.form.Action.Load',
94812     alias: 'formaction.load',
94813
94814     type: 'load',
94815
94816     /**
94817      * @private
94818      */
94819     run: function() {
94820         Ext.Ajax.request(Ext.apply(
94821             this.createCallback(),
94822             {
94823                 method: this.getMethod(),
94824                 url: this.getUrl(),
94825                 headers: this.headers,
94826                 params: this.getParams()
94827             }
94828         ));
94829     },
94830
94831     /**
94832      * @private
94833      */
94834     onSuccess: function(response){
94835         var result = this.processResponse(response),
94836             form = this.form;
94837         if (result === true || !result.success || !result.data) {
94838             this.failureType = Ext.form.action.Action.LOAD_FAILURE;
94839             form.afterAction(this, false);
94840             return;
94841         }
94842         form.clearInvalid();
94843         form.setValues(result.data);
94844         form.afterAction(this, true);
94845     },
94846
94847     /**
94848      * @private
94849      */
94850     handleResponse: function(response) {
94851         var reader = this.form.reader,
94852             rs, data;
94853         if (reader) {
94854             rs = reader.read(response);
94855             data = rs.records && rs.records[0] ? rs.records[0].data : null;
94856             return {
94857                 success : rs.success,
94858                 data : data
94859             };
94860         }
94861         return Ext.decode(response.responseText);
94862     }
94863 });
94864
94865
94866 /**
94867  * A specialized panel intended for use as an application window. Windows are floated, {@link #resizable}, and
94868  * {@link #draggable} by default. Windows can be {@link #maximizable maximized} to fill the viewport, restored to
94869  * their prior size, and can be {@link #minimize}d.
94870  *
94871  * Windows can also be linked to a {@link Ext.ZIndexManager} or managed by the {@link Ext.WindowManager} to provide
94872  * grouping, activation, to front, to back and other application-specific behavior.
94873  *
94874  * By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element specify
94875  * {@link Ext.Component#renderTo renderTo}.
94876  *
94877  * **As with all {@link Ext.container.Container Container}s, it is important to consider how you want the Window to size
94878  * and arrange any child Components. Choose an appropriate {@link #layout} configuration which lays out child Components
94879  * in the required manner.**
94880  *
94881  *     @example
94882  *     Ext.create('Ext.window.Window', {
94883  *         title: 'Hello',
94884  *         height: 200,
94885  *         width: 400,
94886  *         layout: 'fit',
94887  *         items: {  // Let's put an empty grid in just to illustrate fit layout
94888  *             xtype: 'grid',
94889  *             border: false,
94890  *             columns: [{header: 'World'}],                 // One header just for show. There's no data,
94891  *             store: Ext.create('Ext.data.ArrayStore', {}) // A dummy empty data store
94892  *         }
94893  *     }).show();
94894  */
94895 Ext.define('Ext.window.Window', {
94896     extend: 'Ext.panel.Panel',
94897
94898     alternateClassName: 'Ext.Window',
94899
94900     requires: ['Ext.util.ComponentDragger', 'Ext.util.Region', 'Ext.EventManager'],
94901
94902     alias: 'widget.window',
94903
94904     /**
94905      * @cfg {Number} x
94906      * The X position of the left edge of the window on initial showing. Defaults to centering the Window within the
94907      * width of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to).
94908      */
94909
94910     /**
94911      * @cfg {Number} y
94912      * The Y position of the top edge of the window on initial showing. Defaults to centering the Window within the
94913      * height of the Window's container {@link Ext.Element Element} (The Element that the Window is rendered to).
94914      */
94915
94916     /**
94917      * @cfg {Boolean} [modal=false]
94918      * True to make the window modal and mask everything behind it when displayed, false to display it without
94919      * restricting access to other UI elements.
94920      */
94921
94922     /**
94923      * @cfg {String/Ext.Element} [animateTarget=null]
94924      * Id or element from which the window should animate while opening.
94925      */
94926
94927     /**
94928      * @cfg {String/Number/Ext.Component} defaultFocus
94929      * Specifies a Component to receive focus when this Window is focused.
94930      *
94931      * This may be one of:
94932      *
94933      *   - The index of a footer Button.
94934      *   - The id or {@link Ext.AbstractComponent#itemId} of a descendant Component.
94935      *   - A Component.
94936      */
94937
94938     /**
94939      * @cfg {Function} onEsc
94940      * Allows override of the built-in processing for the escape key. Default action is to close the Window (performing
94941      * whatever action is specified in {@link #closeAction}. To prevent the Window closing when the escape key is
94942      * pressed, specify this as {@link Ext#emptyFn Ext.emptyFn}.
94943      */
94944
94945     /**
94946      * @cfg {Boolean} [collapsed=false]
94947      * True to render the window collapsed, false to render it expanded. Note that if {@link #expandOnShow}
94948      * is true (the default) it will override the `collapsed` config and the window will always be
94949      * expanded when shown.
94950      */
94951
94952     /**
94953      * @cfg {Boolean} [maximized=false]
94954      * True to initially display the window in a maximized state.
94955      */
94956
94957     /**
94958     * @cfg {String} [baseCls='x-window']
94959     * The base CSS class to apply to this panel's element.
94960     */
94961     baseCls: Ext.baseCSSPrefix + 'window',
94962
94963     /**
94964      * @cfg {Boolean/Object} resizable
94965      * Specify as `true` to allow user resizing at each edge and corner of the window, false to disable resizing.
94966      *
94967      * This may also be specified as a config object to Ext.resizer.Resizer
94968      */
94969     resizable: true,
94970
94971     /**
94972      * @cfg {Boolean} draggable
94973      * True to allow the window to be dragged by the header bar, false to disable dragging. Note that
94974      * by default the window will be centered in the viewport, so if dragging is disabled the window may need to be
94975      * positioned programmatically after render (e.g., myWindow.setPosition(100, 100);).
94976      */
94977     draggable: true,
94978
94979     /**
94980      * @cfg {Boolean} constrain
94981      * True to constrain the window within its containing element, false to allow it to fall outside of its containing
94982      * element. By default the window will be rendered to document.body. To render and constrain the window within
94983      * another element specify {@link #renderTo}. Optionally the header only can be constrained
94984      * using {@link #constrainHeader}.
94985      */
94986     constrain: false,
94987
94988     /**
94989      * @cfg {Boolean} constrainHeader
94990      * True to constrain the window header within its containing element (allowing the window body to fall outside of
94991      * its containing element) or false to allow the header to fall outside its containing element.
94992      * Optionally the entire window can be constrained using {@link #constrain}.
94993      */
94994     constrainHeader: false,
94995
94996     /**
94997      * @cfg {Boolean} plain
94998      * True to render the window body with a transparent background so that it will blend into the framing elements,
94999      * false to add a lighter background color to visually highlight the body element and separate it more distinctly
95000      * from the surrounding frame.
95001      */
95002     plain: false,
95003
95004     /**
95005      * @cfg {Boolean} minimizable
95006      * True to display the 'minimize' tool button and allow the user to minimize the window, false to hide the button
95007      * and disallow minimizing the window. Note that this button provides no implementation -- the
95008      * behavior of minimizing a window is implementation-specific, so the minimize event must be handled and a custom
95009      * minimize behavior implemented for this option to be useful.
95010      */
95011     minimizable: false,
95012
95013     /**
95014      * @cfg {Boolean} maximizable
95015      * True to display the 'maximize' tool button and allow the user to maximize the window, false to hide the button
95016      * and disallow maximizing the window. Note that when a window is maximized, the tool button
95017      * will automatically change to a 'restore' button with the appropriate behavior already built-in that will restore
95018      * the window to its previous size.
95019      */
95020     maximizable: false,
95021
95022     // inherit docs
95023     minHeight: 100,
95024
95025     // inherit docs
95026     minWidth: 200,
95027
95028     /**
95029      * @cfg {Boolean} expandOnShow
95030      * True to always expand the window when it is displayed, false to keep it in its current state (which may be
95031      * {@link #collapsed}) when displayed.
95032      */
95033     expandOnShow: true,
95034
95035     // inherited docs, same default
95036     collapsible: false,
95037
95038     /**
95039      * @cfg {Boolean} closable
95040      * True to display the 'close' tool button and allow the user to close the window, false to hide the button and
95041      * disallow closing the window.
95042      *
95043      * By default, when close is requested by either clicking the close button in the header or pressing ESC when the
95044      * Window has focus, the {@link #close} method will be called. This will _{@link Ext.Component#destroy destroy}_ the
95045      * Window and its content meaning that it may not be reused.
95046      *
95047      * To make closing a Window _hide_ the Window so that it may be reused, set {@link #closeAction} to 'hide'.
95048      */
95049     closable: true,
95050
95051     /**
95052      * @cfg {Boolean} hidden
95053      * Render this Window hidden. If `true`, the {@link #hide} method will be called internally.
95054      */
95055     hidden: true,
95056
95057     // Inherit docs from Component. Windows render to the body on first show.
95058     autoRender: true,
95059
95060     // Inherit docs from Component. Windows hide using visibility.
95061     hideMode: 'visibility',
95062
95063     /** @cfg {Boolean} floating @hide Windows are always floating*/
95064     floating: true,
95065
95066     ariaRole: 'alertdialog',
95067
95068     itemCls: 'x-window-item',
95069
95070     overlapHeader: true,
95071
95072     ignoreHeaderBorderManagement: true,
95073
95074     // private
95075     initComponent: function() {
95076         var me = this;
95077         me.callParent();
95078         me.addEvents(
95079             /**
95080              * @event activate
95081              * Fires after the window has been visually activated via {@link #setActive}.
95082              * @param {Ext.window.Window} this
95083              */
95084
95085             /**
95086              * @event deactivate
95087              * Fires after the window has been visually deactivated via {@link #setActive}.
95088              * @param {Ext.window.Window} this
95089              */
95090
95091             /**
95092              * @event resize
95093              * Fires after the window has been resized.
95094              * @param {Ext.window.Window} this
95095              * @param {Number} width The window's new width
95096              * @param {Number} height The window's new height
95097              */
95098             'resize',
95099
95100             /**
95101              * @event maximize
95102              * Fires after the window has been maximized.
95103              * @param {Ext.window.Window} this
95104              */
95105             'maximize',
95106
95107             /**
95108              * @event minimize
95109              * Fires after the window has been minimized.
95110              * @param {Ext.window.Window} this
95111              */
95112             'minimize',
95113
95114             /**
95115              * @event restore
95116              * Fires after the window has been restored to its original size after being maximized.
95117              * @param {Ext.window.Window} this
95118              */
95119             'restore'
95120         );
95121
95122         if (me.plain) {
95123             me.addClsWithUI('plain');
95124         }
95125
95126         if (me.modal) {
95127             me.ariaRole = 'dialog';
95128         }
95129     },
95130
95131     // State Management
95132     // private
95133
95134     initStateEvents: function(){
95135         var events = this.stateEvents;
95136         // push on stateEvents if they don't exist
95137         Ext.each(['maximize', 'restore', 'resize', 'dragend'], function(event){
95138             if (Ext.Array.indexOf(events, event)) {
95139                 events.push(event);
95140             }
95141         });
95142         this.callParent();
95143     },
95144
95145     getState: function() {
95146         var me = this,
95147             state = me.callParent() || {},
95148             maximized = !!me.maximized;
95149
95150         state.maximized = maximized;
95151         Ext.apply(state, {
95152             size: maximized ? me.restoreSize : me.getSize(),
95153             pos: maximized ? me.restorePos : me.getPosition()
95154         });
95155         return state;
95156     },
95157
95158     applyState: function(state){
95159         var me = this;
95160
95161         if (state) {
95162             me.maximized = state.maximized;
95163             if (me.maximized) {
95164                 me.hasSavedRestore = true;
95165                 me.restoreSize = state.size;
95166                 me.restorePos = state.pos;
95167             } else {
95168                 Ext.apply(me, {
95169                     width: state.size.width,
95170                     height: state.size.height,
95171                     x: state.pos[0],
95172                     y: state.pos[1]
95173                 });
95174             }
95175         }
95176     },
95177
95178     // private
95179     onMouseDown: function (e) {
95180         var preventFocus;
95181             
95182         if (this.floating) {
95183             if (Ext.fly(e.getTarget()).focusable()) {
95184                 preventFocus = true;
95185             }
95186             this.toFront(preventFocus);
95187         }
95188     },
95189
95190     // private
95191     onRender: function(ct, position) {
95192         var me = this;
95193         me.callParent(arguments);
95194         me.focusEl = me.el;
95195
95196         // Double clicking a header will toggleMaximize
95197         if (me.maximizable) {
95198             me.header.on({
95199                 dblclick: {
95200                     fn: me.toggleMaximize,
95201                     element: 'el',
95202                     scope: me
95203                 }
95204             });
95205         }
95206     },
95207
95208     // private
95209     afterRender: function() {
95210         var me = this,
95211             hidden = me.hidden,
95212             keyMap;
95213
95214         me.hidden = false;
95215         // Component's afterRender sizes and positions the Component
95216         me.callParent();
95217         me.hidden = hidden;
95218
95219         // Create the proxy after the size has been applied in Component.afterRender
95220         me.proxy = me.getProxy();
95221
95222         // clickToRaise
95223         me.mon(me.el, 'mousedown', me.onMouseDown, me);
95224         
95225         // allow the element to be focusable
95226         me.el.set({
95227             tabIndex: -1
95228         });
95229
95230         // Initialize
95231         if (me.maximized) {
95232             me.maximized = false;
95233             me.maximize();
95234         }
95235
95236         if (me.closable) {
95237             keyMap = me.getKeyMap();
95238             keyMap.on(27, me.onEsc, me);
95239
95240             //if (hidden) { ? would be consistent w/before/afterShow...
95241                 keyMap.disable();
95242             //}
95243         }
95244
95245         if (!hidden) {
95246             me.syncMonitorWindowResize();
95247             me.doConstrain();
95248         }
95249     },
95250
95251     /**
95252      * @private
95253      * @override
95254      * Override Component.initDraggable.
95255      * Window uses the header element as the delegate.
95256      */
95257     initDraggable: function() {
95258         var me = this,
95259             ddConfig;
95260
95261         if (!me.header) {
95262             me.updateHeader(true);
95263         }
95264
95265         /*
95266          * Check the header here again. If for whatever reason it wasn't created in
95267          * updateHeader (preventHeader) then we'll just ignore the rest since the
95268          * header acts as the drag handle.
95269          */
95270         if (me.header) {
95271             ddConfig = Ext.applyIf({
95272                 el: me.el,
95273                 delegate: '#' + me.header.id
95274             }, me.draggable);
95275
95276             // Add extra configs if Window is specified to be constrained
95277             if (me.constrain || me.constrainHeader) {
95278                 ddConfig.constrain = me.constrain;
95279                 ddConfig.constrainDelegate = me.constrainHeader;
95280                 ddConfig.constrainTo = me.constrainTo || me.container;
95281             }
95282
95283             /**
95284              * @property {Ext.util.ComponentDragger} dd
95285              * If this Window is configured {@link #draggable}, this property will contain an instance of
95286              * {@link Ext.util.ComponentDragger} (A subclass of {@link Ext.dd.DragTracker DragTracker}) which handles dragging
95287              * the Window's DOM Element, and constraining according to the {@link #constrain} and {@link #constrainHeader} .
95288              *
95289              * This has implementations of `onBeforeStart`, `onDrag` and `onEnd` which perform the dragging action. If
95290              * extra logic is needed at these points, use {@link Ext.Function#createInterceptor createInterceptor} or
95291              * {@link Ext.Function#createSequence createSequence} to augment the existing implementations.
95292              */
95293             me.dd = Ext.create('Ext.util.ComponentDragger', this, ddConfig);
95294             me.relayEvents(me.dd, ['dragstart', 'drag', 'dragend']);
95295         }
95296     },
95297
95298     // private
95299     onEsc: function(k, e) {
95300         e.stopEvent();
95301         this[this.closeAction]();
95302     },
95303
95304     // private
95305     beforeDestroy: function() {
95306         var me = this;
95307         if (me.rendered) {
95308             delete this.animateTarget;
95309             me.hide();
95310             Ext.destroy(
95311                 me.keyMap
95312             );
95313         }
95314         me.callParent();
95315     },
95316
95317     /**
95318      * @private
95319      * @override
95320      * Contribute class-specific tools to the header.
95321      * Called by Panel's initTools.
95322      */
95323     addTools: function() {
95324         var me = this;
95325
95326         // Call Panel's initTools
95327         me.callParent();
95328
95329         if (me.minimizable) {
95330             me.addTool({
95331                 type: 'minimize',
95332                 handler: Ext.Function.bind(me.minimize, me, [])
95333             });
95334         }
95335         if (me.maximizable) {
95336             me.addTool({
95337                 type: 'maximize',
95338                 handler: Ext.Function.bind(me.maximize, me, [])
95339             });
95340             me.addTool({
95341                 type: 'restore',
95342                 handler: Ext.Function.bind(me.restore, me, []),
95343                 hidden: true
95344             });
95345         }
95346     },
95347
95348     /**
95349      * Gets the configured default focus item. If a {@link #defaultFocus} is set, it will receive focus, otherwise the
95350      * Container itself will receive focus.
95351      */
95352     getFocusEl: function() {
95353         var me = this,
95354             f = me.focusEl,
95355             defaultComp = me.defaultButton || me.defaultFocus,
95356             t = typeof db,
95357             el,
95358             ct;
95359
95360         if (Ext.isDefined(defaultComp)) {
95361             if (Ext.isNumber(defaultComp)) {
95362                 f = me.query('button')[defaultComp];
95363             } else if (Ext.isString(defaultComp)) {
95364                 f = me.down('#' + defaultComp);
95365             } else {
95366                 f = defaultComp;
95367             }
95368         }
95369         return f || me.focusEl;
95370     },
95371
95372     // private
95373     beforeShow: function() {
95374         this.callParent();
95375
95376         if (this.expandOnShow) {
95377             this.expand(false);
95378         }
95379     },
95380
95381     // private
95382     afterShow: function(animateTarget) {
95383         var me = this,
95384             animating = animateTarget || me.animateTarget;
95385
95386
95387         // No constraining code needs to go here.
95388         // Component.onShow constrains the Component. *If the constrain config is true*
95389
95390         // Perform superclass's afterShow tasks
95391         // Which might include animating a proxy from an animateTarget
95392         me.callParent(arguments);
95393
95394         if (me.maximized) {
95395             me.fitContainer();
95396         }
95397
95398         me.syncMonitorWindowResize();
95399         if (!animating) {
95400             me.doConstrain();
95401         }
95402
95403         if (me.keyMap) {
95404             me.keyMap.enable();
95405         }
95406     },
95407
95408     // private
95409     doClose: function() {
95410         var me = this;
95411
95412         // Being called as callback after going through the hide call below
95413         if (me.hidden) {
95414             me.fireEvent('close', me);
95415             if (me.closeAction == 'destroy') {
95416                 this.destroy();
95417             }
95418         } else {
95419             // close after hiding
95420             me.hide(me.animateTarget, me.doClose, me);
95421         }
95422     },
95423
95424     // private
95425     afterHide: function() {
95426         var me = this;
95427
95428         // No longer subscribe to resizing now that we're hidden
95429         me.syncMonitorWindowResize();
95430
95431         // Turn off keyboard handling once window is hidden
95432         if (me.keyMap) {
95433             me.keyMap.disable();
95434         }
95435
95436         // Perform superclass's afterHide tasks.
95437         me.callParent(arguments);
95438     },
95439
95440     // private
95441     onWindowResize: function() {
95442         if (this.maximized) {
95443             this.fitContainer();
95444         }
95445         this.doConstrain();
95446     },
95447
95448     /**
95449      * Placeholder method for minimizing the window. By default, this method simply fires the {@link #minimize} event
95450      * since the behavior of minimizing a window is application-specific. To implement custom minimize behavior, either
95451      * the minimize event can be handled or this method can be overridden.
95452      * @return {Ext.window.Window} this
95453      */
95454     minimize: function() {
95455         this.fireEvent('minimize', this);
95456         return this;
95457     },
95458
95459     afterCollapse: function() {
95460         var me = this;
95461
95462         if (me.maximizable) {
95463             me.tools.maximize.hide();
95464             me.tools.restore.hide();
95465         }
95466         if (me.resizer) {
95467             me.resizer.disable();
95468         }
95469         me.callParent(arguments);
95470     },
95471
95472     afterExpand: function() {
95473         var me = this;
95474
95475         if (me.maximized) {
95476             me.tools.restore.show();
95477         } else if (me.maximizable) {
95478             me.tools.maximize.show();
95479         }
95480         if (me.resizer) {
95481             me.resizer.enable();
95482         }
95483         me.callParent(arguments);
95484     },
95485
95486     /**
95487      * Fits the window within its current container and automatically replaces the {@link #maximizable 'maximize' tool
95488      * button} with the 'restore' tool button. Also see {@link #toggleMaximize}.
95489      * @return {Ext.window.Window} this
95490      */
95491     maximize: function() {
95492         var me = this;
95493
95494         if (!me.maximized) {
95495             me.expand(false);
95496             if (!me.hasSavedRestore) {
95497                 me.restoreSize = me.getSize();
95498                 me.restorePos = me.getPosition(true);
95499             }
95500             if (me.maximizable) {
95501                 me.tools.maximize.hide();
95502                 me.tools.restore.show();
95503             }
95504             me.maximized = true;
95505             me.el.disableShadow();
95506
95507             if (me.dd) {
95508                 me.dd.disable();
95509             }
95510             if (me.collapseTool) {
95511                 me.collapseTool.hide();
95512             }
95513             me.el.addCls(Ext.baseCSSPrefix + 'window-maximized');
95514             me.container.addCls(Ext.baseCSSPrefix + 'window-maximized-ct');
95515
95516             me.syncMonitorWindowResize();
95517             me.setPosition(0, 0);
95518             me.fitContainer();
95519             me.fireEvent('maximize', me);
95520         }
95521         return me;
95522     },
95523
95524     /**
95525      * Restores a {@link #maximizable maximized} window back to its original size and position prior to being maximized
95526      * and also replaces the 'restore' tool button with the 'maximize' tool button. Also see {@link #toggleMaximize}.
95527      * @return {Ext.window.Window} this
95528      */
95529     restore: function() {
95530         var me = this,
95531             tools = me.tools;
95532
95533         if (me.maximized) {
95534             delete me.hasSavedRestore;
95535             me.removeCls(Ext.baseCSSPrefix + 'window-maximized');
95536
95537             // Toggle tool visibility
95538             if (tools.restore) {
95539                 tools.restore.hide();
95540             }
95541             if (tools.maximize) {
95542                 tools.maximize.show();
95543             }
95544             if (me.collapseTool) {
95545                 me.collapseTool.show();
95546             }
95547
95548             // Restore the position/sizing
95549             me.setPosition(me.restorePos);
95550             me.setSize(me.restoreSize);
95551
95552             // Unset old position/sizing
95553             delete me.restorePos;
95554             delete me.restoreSize;
95555
95556             me.maximized = false;
95557
95558             me.el.enableShadow(true);
95559
95560             // Allow users to drag and drop again
95561             if (me.dd) {
95562                 me.dd.enable();
95563             }
95564
95565             me.container.removeCls(Ext.baseCSSPrefix + 'window-maximized-ct');
95566
95567             me.syncMonitorWindowResize();
95568             me.doConstrain();
95569             me.fireEvent('restore', me);
95570         }
95571         return me;
95572     },
95573
95574     /**
95575      * Synchronizes the presence of our listener for window resize events. This method
95576      * should be called whenever this status might change.
95577      * @private
95578      */
95579     syncMonitorWindowResize: function () {
95580         var me = this,
95581             currentlyMonitoring = me._monitoringResize,
95582             // all the states where we should be listening to window resize:
95583             yes = me.monitorResize || me.constrain || me.constrainHeader || me.maximized,
95584             // all the states where we veto this:
95585             veto = me.hidden || me.destroying || me.isDestroyed;
95586
95587         if (yes && !veto) {
95588             // we should be listening...
95589             if (!currentlyMonitoring) {
95590                 // but we aren't, so set it up
95591                 Ext.EventManager.onWindowResize(me.onWindowResize, me);
95592                 me._monitoringResize = true;
95593             }
95594         } else if (currentlyMonitoring) {
95595             // we should not be listening, but we are, so tear it down
95596             Ext.EventManager.removeResizeListener(me.onWindowResize, me);
95597             me._monitoringResize = false;
95598         }
95599     },
95600
95601     /**
95602      * A shortcut method for toggling between {@link #maximize} and {@link #restore} based on the current maximized
95603      * state of the window.
95604      * @return {Ext.window.Window} this
95605      */
95606     toggleMaximize: function() {
95607         return this[this.maximized ? 'restore': 'maximize']();
95608     }
95609
95610     /**
95611      * @cfg {Boolean} autoWidth @hide
95612      * Absolute positioned element and therefore cannot support autoWidth.
95613      * A width is a required configuration.
95614      **/
95615 });
95616
95617 /**
95618  * @docauthor Jason Johnston <jason@sencha.com>
95619  *
95620  * Base class for form fields that provides default event handling, rendering, and other common functionality
95621  * needed by all form field types. Utilizes the {@link Ext.form.field.Field} mixin for value handling and validation,
95622  * and the {@link Ext.form.Labelable} mixin to provide label and error message display.
95623  *
95624  * In most cases you will want to use a subclass, such as {@link Ext.form.field.Text} or {@link Ext.form.field.Checkbox},
95625  * rather than creating instances of this class directly. However if you are implementing a custom form field,
95626  * using this as the parent class is recommended.
95627  *
95628  * # Values and Conversions
95629  *
95630  * Because BaseField implements the Field mixin, it has a main value that can be initialized with the
95631  * {@link #value} config and manipulated via the {@link #getValue} and {@link #setValue} methods. This main
95632  * value can be one of many data types appropriate to the current field, for instance a {@link Ext.form.field.Date Date}
95633  * field would use a JavaScript Date object as its value type. However, because the field is rendered as a HTML
95634  * input, this value data type can not always be directly used in the rendered field.
95635  *
95636  * Therefore BaseField introduces the concept of a "raw value". This is the value of the rendered HTML input field,
95637  * and is normally a String. The {@link #getRawValue} and {@link #setRawValue} methods can be used to directly
95638  * work with the raw value, though it is recommended to use getValue and setValue in most cases.
95639  *
95640  * Conversion back and forth between the main value and the raw value is handled by the {@link #valueToRaw} and
95641  * {@link #rawToValue} methods. If you are implementing a subclass that uses a non-String value data type, you
95642  * should override these methods to handle the conversion.
95643  *
95644  * # Rendering
95645  *
95646  * The content of the field body is defined by the {@link #fieldSubTpl} XTemplate, with its argument data
95647  * created by the {@link #getSubTplData} method. Override this template and/or method to create custom
95648  * field renderings.
95649  *
95650  * # Example usage:
95651  *
95652  *     @example
95653  *     // A simple subclass of BaseField that creates a HTML5 search field. Redirects to the
95654  *     // searchUrl when the Enter key is pressed.222
95655  *     Ext.define('Ext.form.SearchField', {
95656  *         extend: 'Ext.form.field.Base',
95657  *         alias: 'widget.searchfield',
95658  *     
95659  *         inputType: 'search',
95660  *     
95661  *         // Config defining the search URL
95662  *         searchUrl: 'http://www.google.com/search?q={0}',
95663  *     
95664  *         // Add specialkey listener
95665  *         initComponent: function() {
95666  *             this.callParent();
95667  *             this.on('specialkey', this.checkEnterKey, this);
95668  *         },
95669  *     
95670  *         // Handle enter key presses, execute the search if the field has a value
95671  *         checkEnterKey: function(field, e) {
95672  *             var value = this.getValue();
95673  *             if (e.getKey() === e.ENTER && !Ext.isEmpty(value)) {
95674  *                 location.href = Ext.String.format(this.searchUrl, value);
95675  *             }
95676  *         }
95677  *     });
95678  *     
95679  *     Ext.create('Ext.form.Panel', {
95680  *         title: 'BaseField Example',
95681  *         bodyPadding: 5,
95682  *         width: 250,
95683  *     
95684  *         // Fields will be arranged vertically, stretched to full width
95685  *         layout: 'anchor',
95686  *         defaults: {
95687  *             anchor: '100%'
95688  *         },
95689  *         items: [{
95690  *             xtype: 'searchfield',
95691  *             fieldLabel: 'Search',
95692  *             name: 'query'
95693  *         }],
95694  *         renderTo: Ext.getBody()
95695  *     });
95696  */
95697 Ext.define('Ext.form.field.Base', {
95698     extend: 'Ext.Component',
95699     mixins: {
95700         labelable: 'Ext.form.Labelable',
95701         field: 'Ext.form.field.Field'
95702     },
95703     alias: 'widget.field',
95704     alternateClassName: ['Ext.form.Field', 'Ext.form.BaseField'],
95705     requires: ['Ext.util.DelayedTask', 'Ext.XTemplate', 'Ext.layout.component.field.Field'],
95706
95707     /**
95708      * @cfg {Ext.XTemplate} fieldSubTpl
95709      * The content of the field body is defined by this config option.
95710      */
95711     fieldSubTpl: [ // note: {id} here is really {inputId}, but {cmpId} is available
95712         '<input id="{id}" type="{type}" ',
95713         '<tpl if="name">name="{name}" </tpl>',
95714         '<tpl if="size">size="{size}" </tpl>',
95715         '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
95716         'class="{fieldCls} {typeCls}" autocomplete="off" />',
95717         {
95718             compiled: true,
95719             disableFormats: true
95720         }
95721     ],
95722
95723     /**
95724      * @cfg {String} name
95725      * The name of the field. This is used as the parameter name when including the field value
95726      * in a {@link Ext.form.Basic#submit form submit()}. If no name is configured, it falls back to the {@link #inputId}.
95727      * To prevent the field from being included in the form submit, set {@link #submitValue} to false.
95728      */
95729
95730     /**
95731      * @cfg {String} inputType
95732      * The type attribute for input fields -- e.g. radio, text, password, file. The extended types
95733      * supported by HTML5 inputs (url, email, etc.) may also be used, though using them will cause older browsers to
95734      * fall back to 'text'.
95735      *
95736      * The type 'password' must be used to render that field type currently -- there is no separate Ext component for
95737      * that. You can use {@link Ext.form.field.File} which creates a custom-rendered file upload field, but if you want
95738      * a plain unstyled file input you can use a BaseField with inputType:'file'.
95739      */
95740     inputType: 'text',
95741
95742     /**
95743      * @cfg {Number} tabIndex
95744      * The tabIndex for this field. Note this only applies to fields that are rendered, not those which are built via
95745      * applyTo
95746      */
95747
95748     /**
95749      * @cfg {String} invalidText
95750      * The error text to use when marking a field invalid and no message is provided
95751      */
95752     invalidText : 'The value in this field is invalid',
95753
95754     /**
95755      * @cfg {String} [fieldCls='x-form-field']
95756      * The default CSS class for the field input
95757      */
95758     fieldCls : Ext.baseCSSPrefix + 'form-field',
95759
95760     /**
95761      * @cfg {String} fieldStyle
95762      * Optional CSS style(s) to be applied to the {@link #inputEl field input element}. Should be a valid argument to
95763      * {@link Ext.Element#applyStyles}. Defaults to undefined. See also the {@link #setFieldStyle} method for changing
95764      * the style after initialization.
95765      */
95766
95767     /**
95768      * @cfg {String} [focusCls='x-form-focus']
95769      * The CSS class to use when the field receives focus
95770      */
95771     focusCls : Ext.baseCSSPrefix + 'form-focus',
95772
95773     /**
95774      * @cfg {String} dirtyCls
95775      * The CSS class to use when the field value {@link #isDirty is dirty}.
95776      */
95777     dirtyCls : Ext.baseCSSPrefix + 'form-dirty',
95778
95779     /**
95780      * @cfg {String[]} checkChangeEvents
95781      * A list of event names that will be listened for on the field's {@link #inputEl input element}, which will cause
95782      * the field's value to be checked for changes. If a change is detected, the {@link #change change event} will be
95783      * fired, followed by validation if the {@link #validateOnChange} option is enabled.
95784      *
95785      * Defaults to ['change', 'propertychange'] in Internet Explorer, and ['change', 'input', 'textInput', 'keyup',
95786      * 'dragdrop'] in other browsers. This catches all the ways that field values can be changed in most supported
95787      * browsers; the only known exceptions at the time of writing are:
95788      *
95789      *   - Safari 3.2 and older: cut/paste in textareas via the context menu, and dragging text into textareas
95790      *   - Opera 10 and 11: dragging text into text fields and textareas, and cut via the context menu in text fields
95791      *     and textareas
95792      *   - Opera 9: Same as Opera 10 and 11, plus paste from context menu in text fields and textareas
95793      *
95794      * If you need to guarantee on-the-fly change notifications including these edge cases, you can call the
95795      * {@link #checkChange} method on a repeating interval, e.g. using {@link Ext.TaskManager}, or if the field is within
95796      * a {@link Ext.form.Panel}, you can use the FormPanel's {@link Ext.form.Panel#pollForChanges} configuration to set up
95797      * such a task automatically.
95798      */
95799     checkChangeEvents: Ext.isIE && (!document.documentMode || document.documentMode < 9) ?
95800                         ['change', 'propertychange'] :
95801                         ['change', 'input', 'textInput', 'keyup', 'dragdrop'],
95802
95803     /**
95804      * @cfg {Number} checkChangeBuffer
95805      * Defines a timeout in milliseconds for buffering {@link #checkChangeEvents} that fire in rapid succession.
95806      * Defaults to 50 milliseconds.
95807      */
95808     checkChangeBuffer: 50,
95809
95810     componentLayout: 'field',
95811
95812     /**
95813      * @cfg {Boolean} readOnly
95814      * true to mark the field as readOnly in HTML.
95815      *
95816      * **Note**: this only sets the element's readOnly DOM attribute. Setting `readOnly=true`, for example, will not
95817      * disable triggering a ComboBox or Date; it gives you the option of forcing the user to choose via the trigger
95818      * without typing in the text box. To hide the trigger use `{@link Ext.form.field.Trigger#hideTrigger hideTrigger}`.
95819      */
95820     readOnly : false,
95821
95822     /**
95823      * @cfg {String} readOnlyCls
95824      * The CSS class applied to the component's main element when it is {@link #readOnly}.
95825      */
95826     readOnlyCls: Ext.baseCSSPrefix + 'form-readonly',
95827
95828     /**
95829      * @cfg {String} inputId
95830      * The id that will be given to the generated input DOM element. Defaults to an automatically generated id. If you
95831      * configure this manually, you must make sure it is unique in the document.
95832      */
95833
95834     /**
95835      * @cfg {Boolean} validateOnBlur
95836      * Whether the field should validate when it loses focus. This will cause fields to be validated
95837      * as the user steps through the fields in the form regardless of whether they are making changes to those fields
95838      * along the way. See also {@link #validateOnChange}.
95839      */
95840     validateOnBlur: true,
95841
95842     // private
95843     hasFocus : false,
95844
95845     baseCls: Ext.baseCSSPrefix + 'field',
95846
95847     maskOnDisable: false,
95848
95849     // private
95850     initComponent : function() {
95851         var me = this;
95852
95853         me.callParent();
95854
95855         me.subTplData = me.subTplData || {};
95856
95857         me.addEvents(
95858             /**
95859              * @event focus
95860              * Fires when this field receives input focus.
95861              * @param {Ext.form.field.Base} this
95862              */
95863             'focus',
95864             /**
95865              * @event blur
95866              * Fires when this field loses input focus.
95867              * @param {Ext.form.field.Base} this
95868              */
95869             'blur',
95870             /**
95871              * @event specialkey
95872              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. To handle other keys
95873              * see {@link Ext.util.KeyMap}. You can check {@link Ext.EventObject#getKey} to determine which key was
95874              * pressed. For example:
95875              *
95876              *     var form = new Ext.form.Panel({
95877              *         ...
95878              *         items: [{
95879              *                 fieldLabel: 'Field 1',
95880              *                 name: 'field1',
95881              *                 allowBlank: false
95882              *             },{
95883              *                 fieldLabel: 'Field 2',
95884              *                 name: 'field2',
95885              *                 listeners: {
95886              *                     specialkey: function(field, e){
95887              *                         // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
95888              *                         // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
95889              *                         if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
95890              *                             var form = field.up('form').getForm();
95891              *                             form.submit();
95892              *                         }
95893              *                     }
95894              *                 }
95895              *             }
95896              *         ],
95897              *         ...
95898              *     });
95899              *
95900              * @param {Ext.form.field.Base} this
95901              * @param {Ext.EventObject} e The event object
95902              */
95903             'specialkey'
95904         );
95905
95906         // Init mixins
95907         me.initLabelable();
95908         me.initField();
95909
95910         // Default name to inputId
95911         if (!me.name) {
95912             me.name = me.getInputId();
95913         }
95914     },
95915
95916     /**
95917      * Returns the input id for this field. If none was specified via the {@link #inputId} config, then an id will be
95918      * automatically generated.
95919      */
95920     getInputId: function() {
95921         return this.inputId || (this.inputId = Ext.id());
95922     },
95923
95924     /**
95925      * Creates and returns the data object to be used when rendering the {@link #fieldSubTpl}.
95926      * @return {Object} The template data
95927      * @template
95928      */
95929     getSubTplData: function() {
95930         var me = this,
95931             type = me.inputType,
95932             inputId = me.getInputId();
95933
95934         return Ext.applyIf(me.subTplData, {
95935             id: inputId,
95936             cmpId: me.id,
95937             name: me.name || inputId,
95938             type: type,
95939             size: me.size || 20,
95940             cls: me.cls,
95941             fieldCls: me.fieldCls,
95942             tabIdx: me.tabIndex,
95943             typeCls: Ext.baseCSSPrefix + 'form-' + (type === 'password' ? 'text' : type)
95944         });
95945     },
95946
95947     afterRender: function() {
95948         this.callParent();
95949         
95950         if (this.inputEl) {
95951             this.inputEl.selectable();
95952         }
95953     },
95954
95955     /**
95956      * Gets the markup to be inserted into the outer template's bodyEl. For fields this is the actual input element.
95957      */
95958     getSubTplMarkup: function() {
95959         return this.getTpl('fieldSubTpl').apply(this.getSubTplData());
95960     },
95961
95962     initRenderTpl: function() {
95963         var me = this;
95964         if (!me.hasOwnProperty('renderTpl')) {
95965             me.renderTpl = me.getTpl('labelableRenderTpl');
95966         }
95967         return me.callParent();
95968     },
95969
95970     initRenderData: function() {
95971         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
95972     },
95973
95974     /**
95975      * Set the {@link #fieldStyle CSS style} of the {@link #inputEl field input element}.
95976      * @param {String/Object/Function} style The style(s) to apply. Should be a valid argument to {@link
95977      * Ext.Element#applyStyles}.
95978      */
95979     setFieldStyle: function(style) {
95980         var me = this,
95981             inputEl = me.inputEl;
95982         if (inputEl) {
95983             inputEl.applyStyles(style);
95984         }
95985         me.fieldStyle = style;
95986     },
95987
95988     // private
95989     onRender : function() {
95990         var me = this,
95991             fieldStyle = me.fieldStyle;
95992
95993         me.onLabelableRender();
95994
95995         /**
95996          * @property {Ext.Element} inputEl
95997          * The input Element for this Field. Only available after the field has been rendered.
95998          */
95999         me.addChildEls({ name: 'inputEl', id: me.getInputId() });
96000
96001         me.callParent(arguments);
96002
96003         // Make the stored rawValue get set as the input element's value
96004         me.setRawValue(me.rawValue);
96005
96006         if (me.readOnly) {
96007             me.setReadOnly(true);
96008         }
96009         if (me.disabled) {
96010             me.disable();
96011         }
96012         if (fieldStyle) {
96013             me.setFieldStyle(fieldStyle);
96014         }
96015
96016         me.renderActiveError();
96017     },
96018
96019     initAria: function() {
96020         var me = this;
96021         me.callParent();
96022
96023         // Associate the field to the error message element
96024         me.getActionEl().dom.setAttribute('aria-describedby', Ext.id(me.errorEl));
96025     },
96026
96027     getFocusEl: function() {
96028         return this.inputEl;
96029     },
96030
96031     isFileUpload: function() {
96032         return this.inputType === 'file';
96033     },
96034
96035     extractFileInput: function() {
96036         var me = this,
96037             fileInput = me.isFileUpload() ? me.inputEl.dom : null,
96038             clone;
96039         if (fileInput) {
96040             clone = fileInput.cloneNode(true);
96041             fileInput.parentNode.replaceChild(clone, fileInput);
96042             me.inputEl = Ext.get(clone);
96043         }
96044         return fileInput;
96045     },
96046
96047     // private override to use getSubmitValue() as a convenience
96048     getSubmitData: function() {
96049         var me = this,
96050             data = null,
96051             val;
96052         if (!me.disabled && me.submitValue && !me.isFileUpload()) {
96053             val = me.getSubmitValue();
96054             if (val !== null) {
96055                 data = {};
96056                 data[me.getName()] = val;
96057             }
96058         }
96059         return data;
96060     },
96061
96062     /**
96063      * Returns the value that would be included in a standard form submit for this field. This will be combined with the
96064      * field's name to form a name=value pair in the {@link #getSubmitData submitted parameters}. If an empty string is
96065      * returned then just the name= will be submitted; if null is returned then nothing will be submitted.
96066      *
96067      * Note that the value returned will have been {@link #processRawValue processed} but may or may not have been
96068      * successfully {@link #validate validated}.
96069      *
96070      * @return {String} The value to be submitted, or null.
96071      */
96072     getSubmitValue: function() {
96073         return this.processRawValue(this.getRawValue());
96074     },
96075
96076     /**
96077      * Returns the raw value of the field, without performing any normalization, conversion, or validation. To get a
96078      * normalized and converted value see {@link #getValue}.
96079      * @return {String} value The raw String value of the field
96080      */
96081     getRawValue: function() {
96082         var me = this,
96083             v = (me.inputEl ? me.inputEl.getValue() : Ext.value(me.rawValue, ''));
96084         me.rawValue = v;
96085         return v;
96086     },
96087
96088     /**
96089      * Sets the field's raw value directly, bypassing {@link #valueToRaw value conversion}, change detection, and
96090      * validation. To set the value with these additional inspections see {@link #setValue}.
96091      * @param {Object} value The value to set
96092      * @return {Object} value The field value that is set
96093      */
96094     setRawValue: function(value) {
96095         var me = this;
96096         value = Ext.value(value, '');
96097         me.rawValue = value;
96098
96099         // Some Field subclasses may not render an inputEl
96100         if (me.inputEl) {
96101             me.inputEl.dom.value = value;
96102         }
96103         return value;
96104     },
96105
96106     /**
96107      * Converts a mixed-type value to a raw representation suitable for displaying in the field. This allows controlling
96108      * how value objects passed to {@link #setValue} are shown to the user, including localization. For instance, for a
96109      * {@link Ext.form.field.Date}, this would control how a Date object passed to {@link #setValue} would be converted
96110      * to a String for display in the field.
96111      *
96112      * See {@link #rawToValue} for the opposite conversion.
96113      *
96114      * The base implementation simply does a standard toString conversion, and converts {@link Ext#isEmpty empty values}
96115      * to an empty string.
96116      *
96117      * @param {Object} value The mixed-type value to convert to the raw representation.
96118      * @return {Object} The converted raw value.
96119      */
96120     valueToRaw: function(value) {
96121         return '' + Ext.value(value, '');
96122     },
96123
96124     /**
96125      * Converts a raw input field value into a mixed-type value that is suitable for this particular field type. This
96126      * allows controlling the normalization and conversion of user-entered values into field-type-appropriate values,
96127      * e.g. a Date object for {@link Ext.form.field.Date}, and is invoked by {@link #getValue}.
96128      *
96129      * It is up to individual implementations to decide how to handle raw values that cannot be successfully converted
96130      * to the desired object type.
96131      *
96132      * See {@link #valueToRaw} for the opposite conversion.
96133      *
96134      * The base implementation does no conversion, returning the raw value untouched.
96135      *
96136      * @param {Object} rawValue
96137      * @return {Object} The converted value.
96138      */
96139     rawToValue: function(rawValue) {
96140         return rawValue;
96141     },
96142
96143     /**
96144      * Performs any necessary manipulation of a raw field value to prepare it for {@link #rawToValue conversion} and/or
96145      * {@link #validate validation}, for instance stripping out ignored characters. In the base implementation it does
96146      * nothing; individual subclasses may override this as needed.
96147      *
96148      * @param {Object} value The unprocessed string value
96149      * @return {Object} The processed string value
96150      */
96151     processRawValue: function(value) {
96152         return value;
96153     },
96154
96155     /**
96156      * Returns the current data value of the field. The type of value returned is particular to the type of the
96157      * particular field (e.g. a Date object for {@link Ext.form.field.Date}), as the result of calling {@link #rawToValue} on
96158      * the field's {@link #processRawValue processed} String value. To return the raw String value, see {@link #getRawValue}.
96159      * @return {Object} value The field value
96160      */
96161     getValue: function() {
96162         var me = this,
96163             val = me.rawToValue(me.processRawValue(me.getRawValue()));
96164         me.value = val;
96165         return val;
96166     },
96167
96168     /**
96169      * Sets a data value into the field and runs the change detection and validation. To set the value directly
96170      * without these inspections see {@link #setRawValue}.
96171      * @param {Object} value The value to set
96172      * @return {Ext.form.field.Field} this
96173      */
96174     setValue: function(value) {
96175         var me = this;
96176         me.setRawValue(me.valueToRaw(value));
96177         return me.mixins.field.setValue.call(me, value);
96178     },
96179
96180
96181     //private
96182     onDisable: function() {
96183         var me = this,
96184             inputEl = me.inputEl;
96185         me.callParent();
96186         if (inputEl) {
96187             inputEl.dom.disabled = true;
96188         }
96189     },
96190
96191     //private
96192     onEnable: function() {
96193         var me = this,
96194             inputEl = me.inputEl;
96195         me.callParent();
96196         if (inputEl) {
96197             inputEl.dom.disabled = false;
96198         }
96199     },
96200
96201     /**
96202      * Sets the read only state of this field.
96203      * @param {Boolean} readOnly Whether the field should be read only.
96204      */
96205     setReadOnly: function(readOnly) {
96206         var me = this,
96207             inputEl = me.inputEl;
96208         if (inputEl) {
96209             inputEl.dom.readOnly = readOnly;
96210             inputEl.dom.setAttribute('aria-readonly', readOnly);
96211         }
96212         me[readOnly ? 'addCls' : 'removeCls'](me.readOnlyCls);
96213         me.readOnly = readOnly;
96214     },
96215
96216     // private
96217     fireKey: function(e){
96218         if(e.isSpecialKey()){
96219             this.fireEvent('specialkey', this, Ext.create('Ext.EventObjectImpl', e));
96220         }
96221     },
96222
96223     // private
96224     initEvents : function(){
96225         var me = this,
96226             inputEl = me.inputEl,
96227             onChangeTask,
96228             onChangeEvent;
96229         if (inputEl) {
96230             me.mon(inputEl, Ext.EventManager.getKeyEvent(), me.fireKey,  me);
96231             me.mon(inputEl, 'focus', me.onFocus, me);
96232
96233             // standardise buffer across all browsers + OS-es for consistent event order.
96234             // (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus)
96235             me.mon(inputEl, 'blur', me.onBlur, me, me.inEditor ? {buffer:10} : null);
96236
96237             // listen for immediate value changes
96238             onChangeTask = Ext.create('Ext.util.DelayedTask', me.checkChange, me);
96239             me.onChangeEvent = onChangeEvent = function() {
96240                 onChangeTask.delay(me.checkChangeBuffer);
96241             };
96242             Ext.each(me.checkChangeEvents, function(eventName) {
96243                 if (eventName === 'propertychange') {
96244                     me.usesPropertychange = true;
96245                 }
96246                 me.mon(inputEl, eventName, onChangeEvent);
96247             }, me);
96248         }
96249         me.callParent();
96250     },
96251
96252     doComponentLayout: function() {
96253         var me = this,
96254             inputEl = me.inputEl,
96255             usesPropertychange = me.usesPropertychange,
96256             ename = 'propertychange',
96257             onChangeEvent = me.onChangeEvent;
96258
96259         // In IE if propertychange is one of the checkChangeEvents, we need to remove
96260         // the listener prior to layout and re-add it after, to prevent it from firing
96261         // needlessly for attribute and style changes applied to the inputEl.
96262         if (usesPropertychange) {
96263             me.mun(inputEl, ename, onChangeEvent);
96264         }
96265         me.callParent(arguments);
96266         if (usesPropertychange) {
96267             me.mon(inputEl, ename, onChangeEvent);
96268         }
96269     },
96270
96271     // private
96272     preFocus: Ext.emptyFn,
96273
96274     // private
96275     onFocus: function() {
96276         var me = this,
96277             focusCls = me.focusCls,
96278             inputEl = me.inputEl;
96279         me.preFocus();
96280         if (focusCls && inputEl) {
96281             inputEl.addCls(focusCls);
96282         }
96283         if (!me.hasFocus) {
96284             me.hasFocus = true;
96285             me.componentLayout.onFocus();
96286             me.fireEvent('focus', me);
96287         }
96288     },
96289
96290     // private
96291     beforeBlur : Ext.emptyFn,
96292
96293     // private
96294     onBlur : function(){
96295         var me = this,
96296             focusCls = me.focusCls,
96297             inputEl = me.inputEl;
96298
96299         if (me.destroying) {
96300             return;
96301         }
96302
96303         me.beforeBlur();
96304         if (focusCls && inputEl) {
96305             inputEl.removeCls(focusCls);
96306         }
96307         if (me.validateOnBlur) {
96308             me.validate();
96309         }
96310         me.hasFocus = false;
96311         me.fireEvent('blur', me);
96312         me.postBlur();
96313     },
96314
96315     // private
96316     postBlur : Ext.emptyFn,
96317
96318
96319     /**
96320      * @private Called when the field's dirty state changes. Adds/removes the {@link #dirtyCls} on the main element.
96321      * @param {Boolean} isDirty
96322      */
96323     onDirtyChange: function(isDirty) {
96324         this[isDirty ? 'addCls' : 'removeCls'](this.dirtyCls);
96325     },
96326
96327
96328     /**
96329      * Returns whether or not the field value is currently valid by {@link #getErrors validating} the
96330      * {@link #processRawValue processed raw value} of the field. **Note**: {@link #disabled} fields are
96331      * always treated as valid.
96332      *
96333      * @return {Boolean} True if the value is valid, else false
96334      */
96335     isValid : function() {
96336         var me = this;
96337         return me.disabled || me.validateValue(me.processRawValue(me.getRawValue()));
96338     },
96339
96340
96341     /**
96342      * Uses {@link #getErrors} to build an array of validation errors. If any errors are found, they are passed to
96343      * {@link #markInvalid} and false is returned, otherwise true is returned.
96344      *
96345      * Previously, subclasses were invited to provide an implementation of this to process validations - from 3.2
96346      * onwards {@link #getErrors} should be overridden instead.
96347      *
96348      * @param {Object} value The value to validate
96349      * @return {Boolean} True if all validations passed, false if one or more failed
96350      */
96351     validateValue: function(value) {
96352         var me = this,
96353             errors = me.getErrors(value),
96354             isValid = Ext.isEmpty(errors);
96355         if (!me.preventMark) {
96356             if (isValid) {
96357                 me.clearInvalid();
96358             } else {
96359                 me.markInvalid(errors);
96360             }
96361         }
96362
96363         return isValid;
96364     },
96365
96366     /**
96367      * Display one or more error messages associated with this field, using {@link #msgTarget} to determine how to
96368      * display the messages and applying {@link #invalidCls} to the field's UI element.
96369      *
96370      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `false`
96371      * if the value does _pass_ validation. So simply marking a Field as invalid will not prevent submission of forms
96372      * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
96373      *
96374      * @param {String/String[]} errors The validation message(s) to display.
96375      */
96376     markInvalid : function(errors) {
96377         // Save the message and fire the 'invalid' event
96378         var me = this,
96379             oldMsg = me.getActiveError();
96380         me.setActiveErrors(Ext.Array.from(errors));
96381         if (oldMsg !== me.getActiveError()) {
96382             me.doComponentLayout();
96383         }
96384     },
96385
96386     /**
96387      * Clear any invalid styles/messages for this field.
96388      *
96389      * **Note**: this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
96390      * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
96391      * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
96392      */
96393     clearInvalid : function() {
96394         // Clear the message and fire the 'valid' event
96395         var me = this,
96396             hadError = me.hasActiveError();
96397         me.unsetActiveError();
96398         if (hadError) {
96399             me.doComponentLayout();
96400         }
96401     },
96402
96403     /**
96404      * @private Overrides the method from the Ext.form.Labelable mixin to also add the invalidCls to the inputEl,
96405      * as that is required for proper styling in IE with nested fields (due to lack of child selector)
96406      */
96407     renderActiveError: function() {
96408         var me = this,
96409             hasError = me.hasActiveError();
96410         if (me.inputEl) {
96411             // Add/remove invalid class
96412             me.inputEl[hasError ? 'addCls' : 'removeCls'](me.invalidCls + '-field');
96413         }
96414         me.mixins.labelable.renderActiveError.call(me);
96415     },
96416
96417
96418     getActionEl: function() {
96419         return this.inputEl || this.el;
96420     }
96421
96422 });
96423
96424 /**
96425  * @docauthor Jason Johnston <jason@sencha.com>
96426  *
96427  * A basic text field.  Can be used as a direct replacement for traditional text inputs,
96428  * or as the base class for more sophisticated input controls (like {@link Ext.form.field.TextArea}
96429  * and {@link Ext.form.field.ComboBox}). Has support for empty-field placeholder values (see {@link #emptyText}).
96430  *
96431  * # Validation
96432  *
96433  * The Text field has a useful set of validations built in:
96434  *
96435  * - {@link #allowBlank} for making the field required
96436  * - {@link #minLength} for requiring a minimum value length
96437  * - {@link #maxLength} for setting a maximum value length (with {@link #enforceMaxLength} to add it
96438  *   as the `maxlength` attribute on the input element)
96439  * - {@link #regex} to specify a custom regular expression for validation
96440  *
96441  * In addition, custom validations may be added:
96442  *
96443  * - {@link #vtype} specifies a virtual type implementation from {@link Ext.form.field.VTypes} which can contain
96444  *   custom validation logic
96445  * - {@link #validator} allows a custom arbitrary function to be called during validation
96446  *
96447  * The details around how and when each of these validation options get used are described in the
96448  * documentation for {@link #getErrors}.
96449  *
96450  * By default, the field value is checked for validity immediately while the user is typing in the
96451  * field. This can be controlled with the {@link #validateOnChange}, {@link #checkChangeEvents}, and
96452  * {@link #checkChangeBuffer} configurations. Also see the details on Form Validation in the
96453  * {@link Ext.form.Panel} class documentation.
96454  *
96455  * # Masking and Character Stripping
96456  *
96457  * Text fields can be configured with custom regular expressions to be applied to entered values before
96458  * validation: see {@link #maskRe} and {@link #stripCharsRe} for details.
96459  *
96460  * # Example usage
96461  *
96462  *     @example
96463  *     Ext.create('Ext.form.Panel', {
96464  *         title: 'Contact Info',
96465  *         width: 300,
96466  *         bodyPadding: 10,
96467  *         renderTo: Ext.getBody(),
96468  *         items: [{
96469  *             xtype: 'textfield',
96470  *             name: 'name',
96471  *             fieldLabel: 'Name',
96472  *             allowBlank: false  // requires a non-empty value
96473  *         }, {
96474  *             xtype: 'textfield',
96475  *             name: 'email',
96476  *             fieldLabel: 'Email Address',
96477  *             vtype: 'email'  // requires value to be a valid email address format
96478  *         }]
96479  *     });
96480  */
96481 Ext.define('Ext.form.field.Text', {
96482     extend:'Ext.form.field.Base',
96483     alias: 'widget.textfield',
96484     requires: ['Ext.form.field.VTypes', 'Ext.layout.component.field.Text'],
96485     alternateClassName: ['Ext.form.TextField', 'Ext.form.Text'],
96486
96487     /**
96488      * @cfg {String} vtypeText
96489      * A custom error message to display in place of the default message provided for the **`{@link #vtype}`** currently
96490      * set for this field. **Note**: only applies if **`{@link #vtype}`** is set, else ignored.
96491      */
96492
96493     /**
96494      * @cfg {RegExp} stripCharsRe
96495      * A JavaScript RegExp object used to strip unwanted content from the value
96496      * before validation. If <tt>stripCharsRe</tt> is specified,
96497      * every character matching <tt>stripCharsRe</tt> will be removed before fed to validation.
96498      * This does not change the value of the field.
96499      */
96500
96501     /**
96502      * @cfg {Number} size
96503      * An initial value for the 'size' attribute on the text input element. This is only used if the field has no
96504      * configured {@link #width} and is not given a width by its container's layout. Defaults to 20.
96505      */
96506     size: 20,
96507
96508     /**
96509      * @cfg {Boolean} [grow=false]
96510      * true if this field should automatically grow and shrink to its content
96511      */
96512
96513     /**
96514      * @cfg {Number} growMin
96515      * The minimum width to allow when `{@link #grow} = true`
96516      */
96517     growMin : 30,
96518
96519     /**
96520      * @cfg {Number} growMax
96521      * The maximum width to allow when `{@link #grow} = true`
96522      */
96523     growMax : 800,
96524
96525     /**
96526      * @cfg {String} growAppend
96527      * A string that will be appended to the field's current value for the purposes of calculating the target field
96528      * size. Only used when the {@link #grow} config is true. Defaults to a single capital "W" (the widest character in
96529      * common fonts) to leave enough space for the next typed character and avoid the field value shifting before the
96530      * width is adjusted.
96531      */
96532     growAppend: 'W',
96533
96534     /**
96535      * @cfg {String} vtype
96536      * A validation type name as defined in {@link Ext.form.field.VTypes}
96537      */
96538
96539     /**
96540      * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes (character being
96541      * typed) that do not match.
96542      * Note: It dose not filter characters already in the input.
96543      */
96544
96545     /**
96546      * @cfg {Boolean} [disableKeyFilter=false]
96547      * Specify true to disable input keystroke filtering
96548      */
96549
96550     /**
96551      * @cfg {Boolean} allowBlank
96552      * Specify false to validate that the value's length is > 0
96553      */
96554     allowBlank : true,
96555
96556     /**
96557      * @cfg {Number} minLength
96558      * Minimum input field length required
96559      */
96560     minLength : 0,
96561
96562     /**
96563      * @cfg {Number} maxLength
96564      * Maximum input field length allowed by validation (defaults to Number.MAX_VALUE). This behavior is intended to
96565      * provide instant feedback to the user by improving usability to allow pasting and editing or overtyping and back
96566      * tracking. To restrict the maximum number of characters that can be entered into the field use the **{@link
96567      * Ext.form.field.Text#enforceMaxLength enforceMaxLength}** option.
96568      */
96569     maxLength : Number.MAX_VALUE,
96570
96571     /**
96572      * @cfg {Boolean} enforceMaxLength
96573      * True to set the maxLength property on the underlying input field. Defaults to false
96574      */
96575
96576     /**
96577      * @cfg {String} minLengthText
96578      * Error text to display if the **{@link #minLength minimum length}** validation fails.
96579      */
96580     minLengthText : 'The minimum length for this field is {0}',
96581
96582     /**
96583      * @cfg {String} maxLengthText
96584      * Error text to display if the **{@link #maxLength maximum length}** validation fails
96585      */
96586     maxLengthText : 'The maximum length for this field is {0}',
96587
96588     /**
96589      * @cfg {Boolean} [selectOnFocus=false]
96590      * true to automatically select any existing field text when the field receives input focus
96591      */
96592
96593     /**
96594      * @cfg {String} blankText
96595      * The error text to display if the **{@link #allowBlank}** validation fails
96596      */
96597     blankText : 'This field is required',
96598
96599     /**
96600      * @cfg {Function} validator
96601      * A custom validation function to be called during field validation ({@link #getErrors}).
96602      * If specified, this function will be called first, allowing the developer to override the default validation
96603      * process.
96604      *
96605      * This function will be passed the following parameters:
96606      *
96607      * @cfg {Object} validator.value The current field value
96608      * @cfg {Boolean/String} validator.return
96609      *
96610      * - True if the value is valid
96611      * - An error message if the value is invalid
96612      */
96613
96614     /**
96615      * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation.
96616      * If the test fails, the field will be marked invalid using
96617      * either <b><tt>{@link #regexText}</tt></b> or <b><tt>{@link #invalidText}</tt></b>.
96618      */
96619
96620     /**
96621      * @cfg {String} regexText
96622      * The error text to display if **{@link #regex}** is used and the test fails during validation
96623      */
96624     regexText : '',
96625
96626     /**
96627      * @cfg {String} emptyText
96628      * The default text to place into an empty field.
96629      *
96630      * Note that normally this value will be submitted to the server if this field is enabled; to prevent this you can
96631      * set the {@link Ext.form.action.Action#submitEmptyText submitEmptyText} option of {@link Ext.form.Basic#submit} to
96632      * false.
96633      *
96634      * Also note that if you use {@link #inputType inputType}:'file', {@link #emptyText} is not supported and should be
96635      * avoided.
96636      */
96637
96638     /**
96639      * @cfg {String} [emptyCls='x-form-empty-field']
96640      * The CSS class to apply to an empty field to style the **{@link #emptyText}**.
96641      * This class is automatically added and removed as needed depending on the current field value.
96642      */
96643     emptyCls : Ext.baseCSSPrefix + 'form-empty-field',
96644
96645     ariaRole: 'textbox',
96646
96647     /**
96648      * @cfg {Boolean} [enableKeyEvents=false]
96649      * true to enable the proxying of key events for the HTML input field
96650      */
96651
96652     componentLayout: 'textfield',
96653
96654     initComponent : function(){
96655         this.callParent();
96656         this.addEvents(
96657             /**
96658              * @event autosize
96659              * Fires when the **{@link #autoSize}** function is triggered and the field is resized according to the
96660              * {@link #grow}/{@link #growMin}/{@link #growMax} configs as a result. This event provides a hook for the
96661              * developer to apply additional logic at runtime to resize the field if needed.
96662              * @param {Ext.form.field.Text} this This text field
96663              * @param {Number} width The new field width
96664              */
96665             'autosize',
96666
96667             /**
96668              * @event keydown
96669              * Keydown input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
96670              * @param {Ext.form.field.Text} this This text field
96671              * @param {Ext.EventObject} e
96672              */
96673             'keydown',
96674             /**
96675              * @event keyup
96676              * Keyup input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
96677              * @param {Ext.form.field.Text} this This text field
96678              * @param {Ext.EventObject} e
96679              */
96680             'keyup',
96681             /**
96682              * @event keypress
96683              * Keypress input field event. This event only fires if **{@link #enableKeyEvents}** is set to true.
96684              * @param {Ext.form.field.Text} this This text field
96685              * @param {Ext.EventObject} e
96686              */
96687             'keypress'
96688         );
96689     },
96690
96691     // private
96692     initEvents : function(){
96693         var me = this,
96694             el = me.inputEl;
96695
96696         me.callParent();
96697         if(me.selectOnFocus || me.emptyText){
96698             me.mon(el, 'mousedown', me.onMouseDown, me);
96699         }
96700         if(me.maskRe || (me.vtype && me.disableKeyFilter !== true && (me.maskRe = Ext.form.field.VTypes[me.vtype+'Mask']))){
96701             me.mon(el, 'keypress', me.filterKeys, me);
96702         }
96703
96704         if (me.enableKeyEvents) {
96705             me.mon(el, {
96706                 scope: me,
96707                 keyup: me.onKeyUp,
96708                 keydown: me.onKeyDown,
96709                 keypress: me.onKeyPress
96710             });
96711         }
96712     },
96713
96714     /**
96715      * @private
96716      * Override. Treat undefined and null values as equal to an empty string value.
96717      */
96718     isEqual: function(value1, value2) {
96719         return this.isEqualAsString(value1, value2);
96720     },
96721
96722     /**
96723      * @private
96724      * If grow=true, invoke the autoSize method when the field's value is changed.
96725      */
96726     onChange: function() {
96727         this.callParent();
96728         this.autoSize();
96729     },
96730
96731     afterRender: function(){
96732         var me = this;
96733         if (me.enforceMaxLength) {
96734             me.inputEl.dom.maxLength = me.maxLength;
96735         }
96736         me.applyEmptyText();
96737         me.autoSize();
96738         me.callParent();
96739     },
96740
96741     onMouseDown: function(e){
96742         var me = this;
96743         if(!me.hasFocus){
96744             me.mon(me.inputEl, 'mouseup', Ext.emptyFn, me, { single: true, preventDefault: true });
96745         }
96746     },
96747
96748     /**
96749      * Performs any necessary manipulation of a raw String value to prepare it for conversion and/or
96750      * {@link #validate validation}. For text fields this applies the configured {@link #stripCharsRe}
96751      * to the raw value.
96752      * @param {String} value The unprocessed string value
96753      * @return {String} The processed string value
96754      */
96755     processRawValue: function(value) {
96756         var me = this,
96757             stripRe = me.stripCharsRe,
96758             newValue;
96759
96760         if (stripRe) {
96761             newValue = value.replace(stripRe, '');
96762             if (newValue !== value) {
96763                 me.setRawValue(newValue);
96764                 value = newValue;
96765             }
96766         }
96767         return value;
96768     },
96769
96770     //private
96771     onDisable: function(){
96772         this.callParent();
96773         if (Ext.isIE) {
96774             this.inputEl.dom.unselectable = 'on';
96775         }
96776     },
96777
96778     //private
96779     onEnable: function(){
96780         this.callParent();
96781         if (Ext.isIE) {
96782             this.inputEl.dom.unselectable = '';
96783         }
96784     },
96785
96786     onKeyDown: function(e) {
96787         this.fireEvent('keydown', this, e);
96788     },
96789
96790     onKeyUp: function(e) {
96791         this.fireEvent('keyup', this, e);
96792     },
96793
96794     onKeyPress: function(e) {
96795         this.fireEvent('keypress', this, e);
96796     },
96797
96798     /**
96799      * Resets the current field value to the originally-loaded value and clears any validation messages.
96800      * Also adds **{@link #emptyText}** and **{@link #emptyCls}** if the original value was blank.
96801      */
96802     reset : function(){
96803         this.callParent();
96804         this.applyEmptyText();
96805     },
96806
96807     applyEmptyText : function(){
96808         var me = this,
96809             emptyText = me.emptyText,
96810             isEmpty;
96811
96812         if (me.rendered && emptyText) {
96813             isEmpty = me.getRawValue().length < 1 && !me.hasFocus;
96814
96815             if (Ext.supports.Placeholder) {
96816                 me.inputEl.dom.placeholder = emptyText;
96817             } else if (isEmpty) {
96818                 me.setRawValue(emptyText);
96819             }
96820
96821             //all browsers need this because of a styling issue with chrome + placeholders.
96822             //the text isnt vertically aligned when empty (and using the placeholder)
96823             if (isEmpty) {
96824                 me.inputEl.addCls(me.emptyCls);
96825             }
96826
96827             me.autoSize();
96828         }
96829     },
96830
96831     // private
96832     preFocus : function(){
96833         var me = this,
96834             inputEl = me.inputEl,
96835             emptyText = me.emptyText,
96836             isEmpty;
96837
96838         if (emptyText && !Ext.supports.Placeholder && inputEl.dom.value === emptyText) {
96839             me.setRawValue('');
96840             isEmpty = true;
96841             inputEl.removeCls(me.emptyCls);
96842         } else if (Ext.supports.Placeholder) {
96843             me.inputEl.removeCls(me.emptyCls);
96844         }
96845         if (me.selectOnFocus || isEmpty) {
96846             inputEl.dom.select();
96847         }
96848     },
96849
96850     onFocus: function() {
96851         var me = this;
96852         me.callParent(arguments);
96853         if (me.emptyText) {
96854             me.autoSize();
96855         }
96856     },
96857
96858     // private
96859     postBlur : function(){
96860         this.applyEmptyText();
96861     },
96862
96863     // private
96864     filterKeys : function(e){
96865         /*
96866          * On European keyboards, the right alt key, Alt Gr, is used to type certain special characters.
96867          * JS detects a keypress of this as ctrlKey & altKey. As such, we check that alt isn't pressed
96868          * so we can still process these special characters.
96869          */
96870         if (e.ctrlKey && !e.altKey) {
96871             return;
96872         }
96873         var key = e.getKey(),
96874             charCode = String.fromCharCode(e.getCharCode());
96875
96876         if(Ext.isGecko && (e.isNavKeyPress() || key === e.BACKSPACE || (key === e.DELETE && e.button === -1))){
96877             return;
96878         }
96879
96880         if(!Ext.isGecko && e.isSpecialKey() && !charCode){
96881             return;
96882         }
96883         if(!this.maskRe.test(charCode)){
96884             e.stopEvent();
96885         }
96886     },
96887
96888     /**
96889      * Returns the raw String value of the field, without performing any normalization, conversion, or validation. Gets
96890      * the current value of the input element if the field has been rendered, ignoring the value if it is the
96891      * {@link #emptyText}. To get a normalized and converted value see {@link #getValue}.
96892      * @return {String} The raw String value of the field
96893      */
96894     getRawValue: function() {
96895         var me = this,
96896             v = me.callParent();
96897         if (v === me.emptyText) {
96898             v = '';
96899         }
96900         return v;
96901     },
96902
96903     /**
96904      * Sets a data value into the field and runs the change detection and validation. Also applies any configured
96905      * {@link #emptyText} for text fields. To set the value directly without these inspections see {@link #setRawValue}.
96906      * @param {Object} value The value to set
96907      * @return {Ext.form.field.Text} this
96908      */
96909     setValue: function(value) {
96910         var me = this,
96911             inputEl = me.inputEl;
96912
96913         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
96914             inputEl.removeCls(me.emptyCls);
96915         }
96916
96917         me.callParent(arguments);
96918
96919         me.applyEmptyText();
96920         return me;
96921     },
96922
96923     /**
96924      * Validates a value according to the field's validation rules and returns an array of errors
96925      * for any failing validations. Validation rules are processed in the following order:
96926      *
96927      * 1. **Field specific validator**
96928      *
96929      *     A validator offers a way to customize and reuse a validation specification.
96930      *     If a field is configured with a `{@link #validator}`
96931      *     function, it will be passed the current field value.  The `{@link #validator}`
96932      *     function is expected to return either:
96933      *
96934      *     - Boolean `true`  if the value is valid (validation continues).
96935      *     - a String to represent the invalid message if invalid (validation halts).
96936      *
96937      * 2. **Basic Validation**
96938      *
96939      *     If the `{@link #validator}` has not halted validation,
96940      *     basic validation proceeds as follows:
96941      *
96942      *     - `{@link #allowBlank}` : (Invalid message = `{@link #emptyText}`)
96943      *
96944      *         Depending on the configuration of `{@link #allowBlank}`, a
96945      *         blank field will cause validation to halt at this step and return
96946      *         Boolean true or false accordingly.
96947      *
96948      *     - `{@link #minLength}` : (Invalid message = `{@link #minLengthText}`)
96949      *
96950      *         If the passed value does not satisfy the `{@link #minLength}`
96951      *         specified, validation halts.
96952      *
96953      *     -  `{@link #maxLength}` : (Invalid message = `{@link #maxLengthText}`)
96954      *
96955      *         If the passed value does not satisfy the `{@link #maxLength}`
96956      *         specified, validation halts.
96957      *
96958      * 3. **Preconfigured Validation Types (VTypes)**
96959      *
96960      *     If none of the prior validation steps halts validation, a field
96961      *     configured with a `{@link #vtype}` will utilize the
96962      *     corresponding {@link Ext.form.field.VTypes VTypes} validation function.
96963      *     If invalid, either the field's `{@link #vtypeText}` or
96964      *     the VTypes vtype Text property will be used for the invalid message.
96965      *     Keystrokes on the field will be filtered according to the VTypes
96966      *     vtype Mask property.
96967      *
96968      * 4. **Field specific regex test**
96969      *
96970      *     If none of the prior validation steps halts validation, a field's
96971      *     configured <code>{@link #regex}</code> test will be processed.
96972      *     The invalid message for this test is configured with `{@link #regexText}`
96973      *
96974      * @param {Object} value The value to validate. The processed raw value will be used if nothing is passed.
96975      * @return {String[]} Array of any validation errors
96976      */
96977     getErrors: function(value) {
96978         var me = this,
96979             errors = me.callParent(arguments),
96980             validator = me.validator,
96981             emptyText = me.emptyText,
96982             allowBlank = me.allowBlank,
96983             vtype = me.vtype,
96984             vtypes = Ext.form.field.VTypes,
96985             regex = me.regex,
96986             format = Ext.String.format,
96987             msg;
96988
96989         value = value || me.processRawValue(me.getRawValue());
96990
96991         if (Ext.isFunction(validator)) {
96992             msg = validator.call(me, value);
96993             if (msg !== true) {
96994                 errors.push(msg);
96995             }
96996         }
96997
96998         if (value.length < 1 || value === emptyText) {
96999             if (!allowBlank) {
97000                 errors.push(me.blankText);
97001             }
97002             //if value is blank, there cannot be any additional errors
97003             return errors;
97004         }
97005
97006         if (value.length < me.minLength) {
97007             errors.push(format(me.minLengthText, me.minLength));
97008         }
97009
97010         if (value.length > me.maxLength) {
97011             errors.push(format(me.maxLengthText, me.maxLength));
97012         }
97013
97014         if (vtype) {
97015             if(!vtypes[vtype](value, me)){
97016                 errors.push(me.vtypeText || vtypes[vtype +'Text']);
97017             }
97018         }
97019
97020         if (regex && !regex.test(value)) {
97021             errors.push(me.regexText || me.invalidText);
97022         }
97023
97024         return errors;
97025     },
97026
97027     /**
97028      * Selects text in this field
97029      * @param {Number} [start=0] The index where the selection should start
97030      * @param {Number} [end] The index where the selection should end (defaults to the text length)
97031      */
97032     selectText : function(start, end){
97033         var me = this,
97034             v = me.getRawValue(),
97035             doFocus = true,
97036             el = me.inputEl.dom,
97037             undef,
97038             range;
97039
97040         if (v.length > 0) {
97041             start = start === undef ? 0 : start;
97042             end = end === undef ? v.length : end;
97043             if (el.setSelectionRange) {
97044                 el.setSelectionRange(start, end);
97045             }
97046             else if(el.createTextRange) {
97047                 range = el.createTextRange();
97048                 range.moveStart('character', start);
97049                 range.moveEnd('character', end - v.length);
97050                 range.select();
97051             }
97052             doFocus = Ext.isGecko || Ext.isOpera;
97053         }
97054         if (doFocus) {
97055             me.focus();
97056         }
97057     },
97058
97059     /**
97060      * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed. This
97061      * only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the width changes.
97062      */
97063     autoSize: function() {
97064         var me = this,
97065             width;
97066         if (me.grow && me.rendered) {
97067             me.doComponentLayout();
97068             width = me.inputEl.getWidth();
97069             if (width !== me.lastInputWidth) {
97070                 me.fireEvent('autosize', width);
97071                 me.lastInputWidth = width;
97072             }
97073         }
97074     },
97075
97076     initAria: function() {
97077         this.callParent();
97078         this.getActionEl().dom.setAttribute('aria-required', this.allowBlank === false);
97079     },
97080
97081     /**
97082      * To get the natural width of the inputEl, we do a simple calculation based on the 'size' config. We use
97083      * hard-coded numbers to approximate what browsers do natively, to avoid having to read any styles which would hurt
97084      * performance. Overrides Labelable method.
97085      * @protected
97086      */
97087     getBodyNaturalWidth: function() {
97088         return Math.round(this.size * 6.5) + 20;
97089     }
97090
97091 });
97092
97093 /**
97094  * @docauthor Robert Dougan <rob@sencha.com>
97095  *
97096  * This class creates a multiline text field, which can be used as a direct replacement for traditional
97097  * textarea fields. In addition, it supports automatically {@link #grow growing} the height of the textarea to
97098  * fit its content.
97099  *
97100  * All of the configuration options from {@link Ext.form.field.Text} can be used on TextArea.
97101  *
97102  * Example usage:
97103  *
97104  *     @example
97105  *     Ext.create('Ext.form.FormPanel', {
97106  *         title      : 'Sample TextArea',
97107  *         width      : 400,
97108  *         bodyPadding: 10,
97109  *         renderTo   : Ext.getBody(),
97110  *         items: [{
97111  *             xtype     : 'textareafield',
97112  *             grow      : true,
97113  *             name      : 'message',
97114  *             fieldLabel: 'Message',
97115  *             anchor    : '100%'
97116  *         }]
97117  *     });
97118  *
97119  * Some other useful configuration options when using {@link #grow} are {@link #growMin} and {@link #growMax}.
97120  * These allow you to set the minimum and maximum grow heights for the textarea.
97121  */
97122 Ext.define('Ext.form.field.TextArea', {
97123     extend:'Ext.form.field.Text',
97124     alias: ['widget.textareafield', 'widget.textarea'],
97125     alternateClassName: 'Ext.form.TextArea',
97126     requires: ['Ext.XTemplate', 'Ext.layout.component.field.TextArea'],
97127
97128     fieldSubTpl: [
97129         '<textarea id="{id}" ',
97130             '<tpl if="name">name="{name}" </tpl>',
97131             '<tpl if="rows">rows="{rows}" </tpl>',
97132             '<tpl if="cols">cols="{cols}" </tpl>',
97133             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
97134             'class="{fieldCls} {typeCls}" ',
97135             'autocomplete="off">',
97136         '</textarea>',
97137         {
97138             compiled: true,
97139             disableFormats: true
97140         }
97141     ],
97142
97143     /**
97144      * @cfg {Number} growMin
97145      * The minimum height to allow when {@link #grow}=true
97146      */
97147     growMin: 60,
97148
97149     /**
97150      * @cfg {Number} growMax
97151      * The maximum height to allow when {@link #grow}=true
97152      */
97153     growMax: 1000,
97154
97155     /**
97156      * @cfg {String} growAppend
97157      * A string that will be appended to the field's current value for the purposes of calculating the target field
97158      * size. Only used when the {@link #grow} config is true. Defaults to a newline for TextArea to ensure there is
97159      * always a space below the current line.
97160      */
97161     growAppend: '\n-',
97162
97163     /**
97164      * @cfg {Number} cols
97165      * An initial value for the 'cols' attribute on the textarea element. This is only used if the component has no
97166      * configured {@link #width} and is not given a width by its container's layout.
97167      */
97168     cols: 20,
97169
97170     /**
97171      * @cfg {Number} cols
97172      * An initial value for the 'cols' attribute on the textarea element. This is only used if the component has no
97173      * configured {@link #width} and is not given a width by its container's layout.
97174      */
97175     rows: 4,
97176
97177     /**
97178      * @cfg {Boolean} enterIsSpecial
97179      * True if you want the enter key to be classed as a special key. Special keys are generally navigation keys
97180      * (arrows, space, enter). Setting the config property to true would mean that you could not insert returns into the
97181      * textarea.
97182      */
97183     enterIsSpecial: false,
97184
97185     /**
97186      * @cfg {Boolean} preventScrollbars
97187      * true to prevent scrollbars from appearing regardless of how much text is in the field. This option is only
97188      * relevant when {@link #grow} is true. Equivalent to setting overflow: hidden.
97189      */
97190     preventScrollbars: false,
97191
97192     // private
97193     componentLayout: 'textareafield',
97194
97195     // private
97196     onRender: function(ct, position) {
97197         var me = this;
97198         Ext.applyIf(me.subTplData, {
97199             cols: me.cols,
97200             rows: me.rows
97201         });
97202
97203         me.callParent(arguments);
97204     },
97205
97206     // private
97207     afterRender: function(){
97208         var me = this;
97209
97210         me.callParent(arguments);
97211
97212         if (me.grow) {
97213             if (me.preventScrollbars) {
97214                 me.inputEl.setStyle('overflow', 'hidden');
97215             }
97216             me.inputEl.setHeight(me.growMin);
97217         }
97218     },
97219
97220     // private
97221     fireKey: function(e) {
97222         if (e.isSpecialKey() && (this.enterIsSpecial || (e.getKey() !== e.ENTER || e.hasModifier()))) {
97223             this.fireEvent('specialkey', this, e);
97224         }
97225     },
97226
97227     /**
97228      * Automatically grows the field to accomodate the height of the text up to the maximum field height allowed. This
97229      * only takes effect if {@link #grow} = true, and fires the {@link #autosize} event if the height changes.
97230      */
97231     autoSize: function() {
97232         var me = this,
97233             height;
97234
97235         if (me.grow && me.rendered) {
97236             me.doComponentLayout();
97237             height = me.inputEl.getHeight();
97238             if (height !== me.lastInputHeight) {
97239                 me.fireEvent('autosize', height);
97240                 me.lastInputHeight = height;
97241             }
97242         }
97243     },
97244
97245     // private
97246     initAria: function() {
97247         this.callParent(arguments);
97248         this.getActionEl().dom.setAttribute('aria-multiline', true);
97249     },
97250
97251     /**
97252      * To get the natural width of the textarea element, we do a simple calculation based on the 'cols' config.
97253      * We use hard-coded numbers to approximate what browsers do natively, to avoid having to read any styles which
97254      * would hurt performance. Overrides Labelable method.
97255      * @protected
97256      */
97257     getBodyNaturalWidth: function() {
97258         return Math.round(this.cols * 6.5) + 20;
97259     }
97260
97261 });
97262
97263
97264 /**
97265  * Utility class for generating different styles of message boxes.  The singleton instance, Ext.MessageBox
97266  * alias `Ext.Msg` can also be used.
97267  *
97268  * Note that a MessageBox is asynchronous.  Unlike a regular JavaScript `alert` (which will halt
97269  * browser execution), showing a MessageBox will not cause the code to stop.  For this reason, if you have code
97270  * that should only run *after* some user feedback from the MessageBox, you must use a callback function
97271  * (see the `function` parameter for {@link #show} for more details).
97272  *
97273  * Basic alert
97274  *
97275  *     @example
97276  *     Ext.Msg.alert('Status', 'Changes saved successfully.');
97277  *
97278  * Prompt for user data and process the result using a callback
97279  *
97280  *     @example
97281  *     Ext.Msg.prompt('Name', 'Please enter your name:', function(btn, text){
97282  *         if (btn == 'ok'){
97283  *             // process text value and close...
97284  *         }
97285  *     });
97286  *
97287  * Show a dialog using config options
97288  *
97289  *     @example
97290  *     Ext.Msg.show({
97291  *          title:'Save Changes?',
97292  *          msg: 'You are closing a tab that has unsaved changes. Would you like to save your changes?',
97293  *          buttons: Ext.Msg.YESNOCANCEL,
97294  *          icon: Ext.Msg.QUESTION
97295  *     });
97296  */
97297 Ext.define('Ext.window.MessageBox', {
97298     extend: 'Ext.window.Window',
97299
97300     requires: [
97301         'Ext.toolbar.Toolbar',
97302         'Ext.form.field.Text',
97303         'Ext.form.field.TextArea',
97304         'Ext.button.Button',
97305         'Ext.layout.container.Anchor',
97306         'Ext.layout.container.HBox',
97307         'Ext.ProgressBar'
97308     ],
97309
97310     alias: 'widget.messagebox',
97311
97312     /**
97313      * Button config that displays a single OK button
97314      * @type Number
97315      */
97316     OK : 1,
97317     /**
97318      * Button config that displays a single Yes button
97319      * @type Number
97320      */
97321     YES : 2,
97322     /**
97323      * Button config that displays a single No button
97324      * @type Number
97325      */
97326     NO : 4,
97327     /**
97328      * Button config that displays a single Cancel button
97329      * @type Number
97330      */
97331     CANCEL : 8,
97332     /**
97333      * Button config that displays OK and Cancel buttons
97334      * @type Number
97335      */
97336     OKCANCEL : 9,
97337     /**
97338      * Button config that displays Yes and No buttons
97339      * @type Number
97340      */
97341     YESNO : 6,
97342     /**
97343      * Button config that displays Yes, No and Cancel buttons
97344      * @type Number
97345      */
97346     YESNOCANCEL : 14,
97347     /**
97348      * The CSS class that provides the INFO icon image
97349      * @type String
97350      */
97351     INFO : 'ext-mb-info',
97352     /**
97353      * The CSS class that provides the WARNING icon image
97354      * @type String
97355      */
97356     WARNING : 'ext-mb-warning',
97357     /**
97358      * The CSS class that provides the QUESTION icon image
97359      * @type String
97360      */
97361     QUESTION : 'ext-mb-question',
97362     /**
97363      * The CSS class that provides the ERROR icon image
97364      * @type String
97365      */
97366     ERROR : 'ext-mb-error',
97367
97368     // hide it by offsets. Windows are hidden on render by default.
97369     hideMode: 'offsets',
97370     closeAction: 'hide',
97371     resizable: false,
97372     title: '&#160;',
97373
97374     width: 600,
97375     height: 500,
97376     minWidth: 250,
97377     maxWidth: 600,
97378     minHeight: 110,
97379     maxHeight: 500,
97380     constrain: true,
97381
97382     cls: Ext.baseCSSPrefix + 'message-box',
97383
97384     layout: {
97385         type: 'anchor'
97386     },
97387
97388     /**
97389      * The default height in pixels of the message box's multiline textarea if displayed.
97390      * @type Number
97391      */
97392     defaultTextHeight : 75,
97393     /**
97394      * The minimum width in pixels of the message box if it is a progress-style dialog.  This is useful
97395      * for setting a different minimum width than text-only dialogs may need.
97396      * @type Number
97397      */
97398     minProgressWidth : 250,
97399     /**
97400      * The minimum width in pixels of the message box if it is a prompt dialog.  This is useful
97401      * for setting a different minimum width than text-only dialogs may need.
97402      * @type Number
97403      */
97404     minPromptWidth: 250,
97405     /**
97406      * An object containing the default button text strings that can be overriden for localized language support.
97407      * Supported properties are: ok, cancel, yes and no.  Generally you should include a locale-specific
97408      * resource file for handling language support across the framework.
97409      * Customize the default text like so: Ext.window.MessageBox.buttonText.yes = "oui"; //french
97410      * @type Object
97411      */
97412     buttonText: {
97413         ok: 'OK',
97414         yes: 'Yes',
97415         no: 'No',
97416         cancel: 'Cancel'
97417     },
97418
97419     buttonIds: [
97420         'ok', 'yes', 'no', 'cancel'
97421     ],
97422
97423     titleText: {
97424         confirm: 'Confirm',
97425         prompt: 'Prompt',
97426         wait: 'Loading...',
97427         alert: 'Attention'
97428     },
97429
97430     iconHeight: 35,
97431
97432     makeButton: function(btnIdx) {
97433         var btnId = this.buttonIds[btnIdx];
97434         return Ext.create('Ext.button.Button', {
97435             handler: this.btnCallback,
97436             itemId: btnId,
97437             scope: this,
97438             text: this.buttonText[btnId],
97439             minWidth: 75
97440         });
97441     },
97442
97443     btnCallback: function(btn) {
97444         var me = this,
97445             value,
97446             field;
97447
97448         if (me.cfg.prompt || me.cfg.multiline) {
97449             if (me.cfg.multiline) {
97450                 field = me.textArea;
97451             } else {
97452                 field = me.textField;
97453             }
97454             value = field.getValue();
97455             field.reset();
97456         }
97457
97458         // Important not to have focus remain in the hidden Window; Interferes with DnD.
97459         btn.blur();
97460         me.hide();
97461         me.userCallback(btn.itemId, value, me.cfg);
97462     },
97463
97464     hide: function() {
97465         var me = this;
97466         me.dd.endDrag();
97467         me.progressBar.reset();
97468         me.removeCls(me.cfg.cls);
97469         me.callParent();
97470     },
97471
97472     initComponent: function() {
97473         var me = this,
97474             i, button;
97475
97476         me.title = '&#160;';
97477
97478         me.topContainer = Ext.create('Ext.container.Container', {
97479             anchor: '100%',
97480             style: {
97481                 padding: '10px',
97482                 overflow: 'hidden'
97483             },
97484             items: [
97485                 me.iconComponent = Ext.create('Ext.Component', {
97486                     cls: 'ext-mb-icon',
97487                     width: 50,
97488                     height: me.iconHeight,
97489                     style: {
97490                         'float': 'left'
97491                     }
97492                 }),
97493                 me.promptContainer = Ext.create('Ext.container.Container', {
97494                     layout: {
97495                         type: 'anchor'
97496                     },
97497                     items: [
97498                         me.msg = Ext.create('Ext.Component', {
97499                             autoEl: { tag: 'span' },
97500                             cls: 'ext-mb-text'
97501                         }),
97502                         me.textField = Ext.create('Ext.form.field.Text', {
97503                             anchor: '100%',
97504                             enableKeyEvents: true,
97505                             listeners: {
97506                                 keydown: me.onPromptKey,
97507                                 scope: me
97508                             }
97509                         }),
97510                         me.textArea = Ext.create('Ext.form.field.TextArea', {
97511                             anchor: '100%',
97512                             height: 75
97513                         })
97514                     ]
97515                 })
97516             ]
97517         });
97518         me.progressBar = Ext.create('Ext.ProgressBar', {
97519             anchor: '-10',
97520             style: 'margin-left:10px'
97521         });
97522
97523         me.items = [me.topContainer, me.progressBar];
97524
97525         // Create the buttons based upon passed bitwise config
97526         me.msgButtons = [];
97527         for (i = 0; i < 4; i++) {
97528             button = me.makeButton(i);
97529             me.msgButtons[button.itemId] = button;
97530             me.msgButtons.push(button);
97531         }
97532         me.bottomTb = Ext.create('Ext.toolbar.Toolbar', {
97533             ui: 'footer',
97534             dock: 'bottom',
97535             layout: {
97536                 pack: 'center'
97537             },
97538             items: [
97539                 me.msgButtons[0],
97540                 me.msgButtons[1],
97541                 me.msgButtons[2],
97542                 me.msgButtons[3]
97543             ]
97544         });
97545         me.dockedItems = [me.bottomTb];
97546
97547         me.callParent();
97548     },
97549
97550     onPromptKey: function(textField, e) {
97551         var me = this,
97552             blur;
97553
97554         if (e.keyCode === Ext.EventObject.RETURN || e.keyCode === 10) {
97555             if (me.msgButtons.ok.isVisible()) {
97556                 blur = true;
97557                 me.msgButtons.ok.handler.call(me, me.msgButtons.ok);
97558             } else if (me.msgButtons.yes.isVisible()) {
97559                 me.msgButtons.yes.handler.call(me, me.msgButtons.yes);
97560                 blur = true;
97561             }
97562
97563             if (blur) {
97564                 me.textField.blur();
97565             }
97566         }
97567     },
97568
97569     reconfigure: function(cfg) {
97570         var me = this,
97571             buttons = cfg.buttons || 0,
97572             hideToolbar = true,
97573             initialWidth = me.maxWidth,
97574             i;
97575
97576         cfg = cfg || {};
97577         me.cfg = cfg;
97578         if (cfg.width) {
97579             initialWidth = cfg.width;
97580         }
97581
97582         // Default to allowing the Window to take focus.
97583         delete me.defaultFocus;
97584
97585         // clear any old animateTarget
97586         me.animateTarget = cfg.animateTarget || undefined;
97587
97588         // Defaults to modal
97589         me.modal = cfg.modal !== false;
97590
97591         // Show the title
97592         if (cfg.title) {
97593             me.setTitle(cfg.title||'&#160;');
97594         }
97595
97596         if (!me.rendered) {
97597             me.width = initialWidth;
97598             me.render(Ext.getBody());
97599         } else {
97600             me.setSize(initialWidth, me.maxHeight);
97601         }
97602         me.setPosition(-10000, -10000);
97603
97604         // Hide or show the close tool
97605         me.closable = cfg.closable && !cfg.wait;
97606         me.header.child('[type=close]').setVisible(cfg.closable !== false);
97607
97608         // Hide or show the header
97609         if (!cfg.title && !me.closable) {
97610             me.header.hide();
97611         } else {
97612             me.header.show();
97613         }
97614
97615         // Default to dynamic drag: drag the window, not a ghost
97616         me.liveDrag = !cfg.proxyDrag;
97617
97618         // wrap the user callback
97619         me.userCallback = Ext.Function.bind(cfg.callback ||cfg.fn || Ext.emptyFn, cfg.scope || Ext.global);
97620
97621         // Hide or show the icon Component
97622         me.setIcon(cfg.icon);
97623
97624         // Hide or show the message area
97625         if (cfg.msg) {
97626             me.msg.update(cfg.msg);
97627             me.msg.show();
97628         } else {
97629             me.msg.hide();
97630         }
97631
97632         // Hide or show the input field
97633         if (cfg.prompt || cfg.multiline) {
97634             me.multiline = cfg.multiline;
97635             if (cfg.multiline) {
97636                 me.textArea.setValue(cfg.value);
97637                 me.textArea.setHeight(cfg.defaultTextHeight || me.defaultTextHeight);
97638                 me.textArea.show();
97639                 me.textField.hide();
97640                 me.defaultFocus = me.textArea;
97641             } else {
97642                 me.textField.setValue(cfg.value);
97643                 me.textArea.hide();
97644                 me.textField.show();
97645                 me.defaultFocus = me.textField;
97646             }
97647         } else {
97648             me.textArea.hide();
97649             me.textField.hide();
97650         }
97651
97652         // Hide or show the progress bar
97653         if (cfg.progress || cfg.wait) {
97654             me.progressBar.show();
97655             me.updateProgress(0, cfg.progressText);
97656             if(cfg.wait === true){
97657                 me.progressBar.wait(cfg.waitConfig);
97658             }
97659         } else {
97660             me.progressBar.hide();
97661         }
97662
97663         // Hide or show buttons depending on flag value sent.
97664         for (i = 0; i < 4; i++) {
97665             if (buttons & Math.pow(2, i)) {
97666
97667                 // Default to focus on the first visible button if focus not already set
97668                 if (!me.defaultFocus) {
97669                     me.defaultFocus = me.msgButtons[i];
97670                 }
97671                 me.msgButtons[i].show();
97672                 hideToolbar = false;
97673             } else {
97674                 me.msgButtons[i].hide();
97675             }
97676         }
97677
97678         // Hide toolbar if no buttons to show
97679         if (hideToolbar) {
97680             me.bottomTb.hide();
97681         } else {
97682             me.bottomTb.show();
97683         }
97684     },
97685
97686     /**
97687      * Displays a new message box, or reinitializes an existing message box, based on the config options
97688      * passed in. All display functions (e.g. prompt, alert, etc.) on MessageBox call this function internally,
97689      * although those calls are basic shortcuts and do not support all of the config options allowed here.
97690      * @param {Object} config The following config options are supported: <ul>
97691      * <li><b>animateTarget</b> : String/Element<div class="sub-desc">An id or Element from which the message box should animate as it
97692      * opens and closes (defaults to undefined)</div></li>
97693      * <li><b>buttons</b> : Number<div class="sub-desc">A bitwise button specifier consisting of the sum of any of the following constants:<ul>
97694      * <li>Ext.window.MessageBox.OK</li>
97695      * <li>Ext.window.MessageBox.YES</li>
97696      * <li>Ext.window.MessageBox.NO</li>
97697      * <li>Ext.window.MessageBox.CANCEL</li>
97698      * </ul>Or false to not show any buttons (defaults to false)</div></li>
97699      * <li><b>closable</b> : Boolean<div class="sub-desc">False to hide the top-right close button (defaults to true). Note that
97700      * progress and wait dialogs will ignore this property and always hide the close button as they can only
97701      * be closed programmatically.</div></li>
97702      * <li><b>cls</b> : String<div class="sub-desc">A custom CSS class to apply to the message box's container element</div></li>
97703      * <li><b>defaultTextHeight</b> : Number<div class="sub-desc">The default height in pixels of the message box's multiline textarea
97704      * if displayed (defaults to 75)</div></li>
97705      * <li><b>fn</b> : Function<div class="sub-desc">A callback function which is called when the dialog is dismissed either
97706      * by clicking on the configured buttons, or on the dialog close button, or by pressing
97707      * the return button to enter input.
97708      * <p>Progress and wait dialogs will ignore this option since they do not respond to user
97709      * actions and can only be closed programmatically, so any required function should be called
97710      * by the same code after it closes the dialog. Parameters passed:<ul>
97711      * <li><b>buttonId</b> : String<div class="sub-desc">The ID of the button pressed, one of:<div class="sub-desc"><ul>
97712      * <li><tt>ok</tt></li>
97713      * <li><tt>yes</tt></li>
97714      * <li><tt>no</tt></li>
97715      * <li><tt>cancel</tt></li>
97716      * </ul></div></div></li>
97717      * <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>
97718      * or <tt><a href="#show-option-multiline" ext:member="show-option-multiline" ext:cls="Ext.window.MessageBox">multiline</a></tt> is true</div></li>
97719      * <li><b>opt</b> : Object<div class="sub-desc">The config object passed to show.</div></li>
97720      * </ul></p></div></li>
97721      * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code>this</code> reference) in which the function will be executed.</div></li>
97722      * <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
97723      * dialog (e.g. Ext.window.MessageBox.WARNING or 'custom-class') (defaults to '')</div></li>
97724      * <li><b>iconCls</b> : String<div class="sub-desc">The standard {@link Ext.window.Window#iconCls} to
97725      * add an optional header icon (defaults to '')</div></li>
97726      * <li><b>maxWidth</b> : Number<div class="sub-desc">The maximum width in pixels of the message box (defaults to 600)</div></li>
97727      * <li><b>minWidth</b> : Number<div class="sub-desc">The minimum width in pixels of the message box (defaults to 100)</div></li>
97728      * <li><b>modal</b> : Boolean<div class="sub-desc">False to allow user interaction with the page while the message box is
97729      * displayed (defaults to true)</div></li>
97730      * <li><b>msg</b> : String<div class="sub-desc">A string that will replace the existing message box body text (defaults to the
97731      * XHTML-compliant non-breaking space character '&amp;#160;')</div></li>
97732      * <li><a id="show-option-multiline"></a><b>multiline</b> : Boolean<div class="sub-desc">
97733      * True to prompt the user to enter multi-line text (defaults to false)</div></li>
97734      * <li><b>progress</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
97735      * <li><b>progressText</b> : String<div class="sub-desc">The text to display inside the progress bar if progress = true (defaults to '')</div></li>
97736      * <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>
97737      * <li><b>proxyDrag</b> : Boolean<div class="sub-desc">True to display a lightweight proxy while dragging (defaults to false)</div></li>
97738      * <li><b>title</b> : String<div class="sub-desc">The title text</div></li>
97739      * <li><b>value</b> : String<div class="sub-desc">The string value to set into the active textbox element if displayed</div></li>
97740      * <li><b>wait</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
97741      * <li><b>waitConfig</b> : Object<div class="sub-desc">A {@link Ext.ProgressBar#wait} config object (applies only if wait = true)</div></li>
97742      * <li><b>width</b> : Number<div class="sub-desc">The width of the dialog in pixels</div></li>
97743      * </ul>
97744      * Example usage:
97745      * <pre><code>
97746 Ext.Msg.show({
97747 title: 'Address',
97748 msg: 'Please enter your address:',
97749 width: 300,
97750 buttons: Ext.Msg.OKCANCEL,
97751 multiline: true,
97752 fn: saveAddress,
97753 animateTarget: 'addAddressBtn',
97754 icon: Ext.window.MessageBox.INFO
97755 });
97756 </code></pre>
97757      * @return {Ext.window.MessageBox} this
97758      */
97759     show: function(cfg) {
97760         var me = this;
97761
97762         me.reconfigure(cfg);
97763         me.addCls(cfg.cls);
97764         if (cfg.animateTarget) {
97765             me.doAutoSize(true);
97766             me.callParent();
97767         } else {
97768             me.callParent();
97769             me.doAutoSize(true);
97770         }
97771         return me;
97772     },
97773
97774     afterShow: function(){
97775         if (this.animateTarget) {
97776             this.center();
97777         }
97778         this.callParent(arguments);
97779     },
97780
97781     doAutoSize: function(center) {
97782         var me = this,
97783             icon = me.iconComponent,
97784             iconHeight = me.iconHeight;
97785
97786         if (!Ext.isDefined(me.frameWidth)) {
97787             me.frameWidth = me.el.getWidth() - me.body.getWidth();
97788         }
97789
97790         // reset to the original dimensions
97791         icon.setHeight(iconHeight);
97792
97793         // Allow per-invocation override of minWidth
97794         me.minWidth = me.cfg.minWidth || Ext.getClass(this).prototype.minWidth;
97795
97796         // Set best possible size based upon allowing the text to wrap in the maximized Window, and
97797         // then constraining it to within the max with. Then adding up constituent element heights.
97798         me.topContainer.doLayout();
97799         if (Ext.isIE6 || Ext.isIEQuirks) {
97800             // In IE quirks, the initial full width of the prompt fields will prevent the container element
97801             // from collapsing once sized down, so temporarily force them to a small width. They'll get
97802             // layed out to their final width later when setting the final window size.
97803             me.textField.setCalculatedSize(9);
97804             me.textArea.setCalculatedSize(9);
97805         }
97806         var width = me.cfg.width || me.msg.getWidth() + icon.getWidth() + 25, /* topContainer's layout padding */
97807             height = (me.header.rendered ? me.header.getHeight() : 0) +
97808             Math.max(me.promptContainer.getHeight(), icon.getHeight()) +
97809             me.progressBar.getHeight() +
97810             (me.bottomTb.rendered ? me.bottomTb.getHeight() : 0) + 20 ;/* topContainer's layout padding */
97811
97812         // Update to the size of the content, this way the text won't wrap under the icon.
97813         icon.setHeight(Math.max(iconHeight, me.msg.getHeight()));
97814         me.setSize(width + me.frameWidth, height + me.frameWidth);
97815         if (center) {
97816             me.center();
97817         }
97818         return me;
97819     },
97820
97821     updateText: function(text) {
97822         this.msg.update(text);
97823         return this.doAutoSize(true);
97824     },
97825
97826     /**
97827      * Adds the specified icon to the dialog.  By default, the class 'ext-mb-icon' is applied for default
97828      * styling, and the class passed in is expected to supply the background image url. Pass in empty string ('')
97829      * to clear any existing icon. This method must be called before the MessageBox is shown.
97830      * The following built-in icon classes are supported, but you can also pass in a custom class name:
97831      * <pre>
97832 Ext.window.MessageBox.INFO
97833 Ext.window.MessageBox.WARNING
97834 Ext.window.MessageBox.QUESTION
97835 Ext.window.MessageBox.ERROR
97836      *</pre>
97837      * @param {String} icon A CSS classname specifying the icon's background image url, or empty string to clear the icon
97838      * @return {Ext.window.MessageBox} this
97839      */
97840     setIcon : function(icon) {
97841         var me = this;
97842         me.iconComponent.removeCls(me.iconCls);
97843         if (icon) {
97844             me.iconComponent.show();
97845             me.iconComponent.addCls(Ext.baseCSSPrefix + 'dlg-icon');
97846             me.iconComponent.addCls(me.iconCls = icon);
97847         } else {
97848             me.iconComponent.removeCls(Ext.baseCSSPrefix + 'dlg-icon');
97849             me.iconComponent.hide();
97850         }
97851         return me;
97852     },
97853
97854     /**
97855      * Updates a progress-style message box's text and progress bar. Only relevant on message boxes
97856      * initiated via {@link Ext.window.MessageBox#progress} or {@link Ext.window.MessageBox#wait},
97857      * or by calling {@link Ext.window.MessageBox#show} with progress: true.
97858      * @param {Number} [value=0] Any number between 0 and 1 (e.g., .5)
97859      * @param {String} [progressText=''] The progress text to display inside the progress bar.
97860      * @param {String} [msg] The message box's body text is replaced with the specified string (defaults to undefined
97861      * so that any existing body text will not get overwritten by default unless a new value is passed in)
97862      * @return {Ext.window.MessageBox} this
97863      */
97864     updateProgress : function(value, progressText, msg){
97865         this.progressBar.updateProgress(value, progressText);
97866         if (msg){
97867             this.updateText(msg);
97868         }
97869         return this;
97870     },
97871
97872     onEsc: function() {
97873         if (this.closable !== false) {
97874             this.callParent(arguments);
97875         }
97876     },
97877
97878     /**
97879      * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm).
97880      * If a callback function is passed it will be called after the user clicks either button,
97881      * and the id of the button that was clicked will be passed as the only parameter to the callback
97882      * (could also be the top-right close button).
97883      * @param {String} title The title bar text
97884      * @param {String} msg The message box body text
97885      * @param {Function} fn (optional) The callback function invoked after the message box is closed
97886      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
97887      * @return {Ext.window.MessageBox} this
97888      */
97889     confirm: function(cfg, msg, fn, scope) {
97890         if (Ext.isString(cfg)) {
97891             cfg = {
97892                 title: cfg,
97893                 icon: 'ext-mb-question',
97894                 msg: msg,
97895                 buttons: this.YESNO,
97896                 callback: fn,
97897                 scope: scope
97898             };
97899         }
97900         return this.show(cfg);
97901     },
97902
97903     /**
97904      * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to JavaScript's prompt).
97905      * The prompt can be a single-line or multi-line textbox.  If a callback function is passed it will be called after the user
97906      * clicks either button, and the id of the button that was clicked (could also be the top-right
97907      * close button) and the text that was entered will be passed as the two parameters to the callback.
97908      * @param {String} title The title bar text
97909      * @param {String} msg The message box body text
97910      * @param {Function} [fn] The callback function invoked after the message box is closed
97911      * @param {Object} [scope] The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
97912      * @param {Boolean/Number} [multiline=false] True to create a multiline textbox using the defaultTextHeight
97913      * property, or the height in pixels to create the textbox/
97914      * @param {String} [value=''] Default value of the text input element
97915      * @return {Ext.window.MessageBox} this
97916      */
97917     prompt : function(cfg, msg, fn, scope, multiline, value){
97918         if (Ext.isString(cfg)) {
97919             cfg = {
97920                 prompt: true,
97921                 title: cfg,
97922                 minWidth: this.minPromptWidth,
97923                 msg: msg,
97924                 buttons: this.OKCANCEL,
97925                 callback: fn,
97926                 scope: scope,
97927                 multiline: multiline,
97928                 value: value
97929             };
97930         }
97931         return this.show(cfg);
97932     },
97933
97934     /**
97935      * Displays a message box with an infinitely auto-updating progress bar.  This can be used to block user
97936      * interaction while waiting for a long-running process to complete that does not have defined intervals.
97937      * You are responsible for closing the message box when the process is complete.
97938      * @param {String} msg The message box body text
97939      * @param {String} title (optional) The title bar text
97940      * @param {Object} config (optional) A {@link Ext.ProgressBar#wait} config object
97941      * @return {Ext.window.MessageBox} this
97942      */
97943     wait : function(cfg, title, config){
97944         if (Ext.isString(cfg)) {
97945             cfg = {
97946                 title : title,
97947                 msg : cfg,
97948                 closable: false,
97949                 wait: true,
97950                 modal: true,
97951                 minWidth: this.minProgressWidth,
97952                 waitConfig: config
97953             };
97954         }
97955         return this.show(cfg);
97956     },
97957
97958     /**
97959      * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt).
97960      * If a callback function is passed it will be called after the user clicks the button, and the
97961      * id of the button that was clicked will be passed as the only parameter to the callback
97962      * (could also be the top-right close button).
97963      * @param {String} title The title bar text
97964      * @param {String} msg The message box body text
97965      * @param {Function} fn (optional) The callback function invoked after the message box is closed
97966      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to the browser wnidow.
97967      * @return {Ext.window.MessageBox} this
97968      */
97969     alert: function(cfg, msg, fn, scope) {
97970         if (Ext.isString(cfg)) {
97971             cfg = {
97972                 title : cfg,
97973                 msg : msg,
97974                 buttons: this.OK,
97975                 fn: fn,
97976                 scope : scope,
97977                 minWidth: this.minWidth
97978             };
97979         }
97980         return this.show(cfg);
97981     },
97982
97983     /**
97984      * Displays a message box with a progress bar.  This message box has no buttons and is not closeable by
97985      * the user.  You are responsible for updating the progress bar as needed via {@link Ext.window.MessageBox#updateProgress}
97986      * and closing the message box when the process is complete.
97987      * @param {String} title The title bar text
97988      * @param {String} msg The message box body text
97989      * @param {String} [progressText=''] The text to display inside the progress bar
97990      * @return {Ext.window.MessageBox} this
97991      */
97992     progress : function(cfg, msg, progressText){
97993         if (Ext.isString(cfg)) {
97994             cfg = {
97995                 title: cfg,
97996                 msg: msg,
97997                 progress: true,
97998                 progressText: progressText
97999             };
98000         }
98001         return this.show(cfg);
98002     }
98003 }, function() {
98004     /**
98005      * @class Ext.MessageBox
98006      * @alternateClassName Ext.Msg
98007      * @extends Ext.window.MessageBox
98008      * @singleton
98009      * Singleton instance of {@link Ext.window.MessageBox}.
98010      */
98011     Ext.MessageBox = Ext.Msg = new this();
98012 });
98013 /**
98014  * @class Ext.form.Basic
98015  * @extends Ext.util.Observable
98016  *
98017  * Provides input field management, validation, submission, and form loading services for the collection
98018  * of {@link Ext.form.field.Field Field} instances within a {@link Ext.container.Container}. It is recommended
98019  * that you use a {@link Ext.form.Panel} as the form container, as that has logic to automatically
98020  * hook up an instance of {@link Ext.form.Basic} (plus other conveniences related to field configuration.)
98021  *
98022  * ## Form Actions
98023  *
98024  * The Basic class delegates the handling of form loads and submits to instances of {@link Ext.form.action.Action}.
98025  * See the various Action implementations for specific details of each one's functionality, as well as the
98026  * documentation for {@link #doAction} which details the configuration options that can be specified in
98027  * each action call.
98028  *
98029  * The default submit Action is {@link Ext.form.action.Submit}, which uses an Ajax request to submit the
98030  * form's values to a configured URL. To enable normal browser submission of an Ext form, use the
98031  * {@link #standardSubmit} config option.
98032  *
98033  * ## File uploads
98034  *
98035  * File uploads are not performed using normal 'Ajax' techniques; see the description for
98036  * {@link #hasUpload} for details. If you're using file uploads you should read the method description.
98037  *
98038  * ## Example usage:
98039  *
98040  *     Ext.create('Ext.form.Panel', {
98041  *         title: 'Basic Form',
98042  *         renderTo: Ext.getBody(),
98043  *         bodyPadding: 5,
98044  *         width: 350,
98045  *
98046  *         // Any configuration items here will be automatically passed along to
98047  *         // the Ext.form.Basic instance when it gets created.
98048  *
98049  *         // The form will submit an AJAX request to this URL when submitted
98050  *         url: 'save-form.php',
98051  *
98052  *         items: [{
98053  *             fieldLabel: 'Field',
98054  *             name: 'theField'
98055  *         }],
98056  *
98057  *         buttons: [{
98058  *             text: 'Submit',
98059  *             handler: function() {
98060  *                 // The getForm() method returns the Ext.form.Basic instance:
98061  *                 var form = this.up('form').getForm();
98062  *                 if (form.isValid()) {
98063  *                     // Submit the Ajax request and handle the response
98064  *                     form.submit({
98065  *                         success: function(form, action) {
98066  *                            Ext.Msg.alert('Success', action.result.msg);
98067  *                         },
98068  *                         failure: function(form, action) {
98069  *                             Ext.Msg.alert('Failed', action.result.msg);
98070  *                         }
98071  *                     });
98072  *                 }
98073  *             }
98074  *         }]
98075  *     });
98076  *
98077  * @docauthor Jason Johnston <jason@sencha.com>
98078  */
98079 Ext.define('Ext.form.Basic', {
98080     extend: 'Ext.util.Observable',
98081     alternateClassName: 'Ext.form.BasicForm',
98082     requires: ['Ext.util.MixedCollection', 'Ext.form.action.Load', 'Ext.form.action.Submit',
98083                'Ext.window.MessageBox', 'Ext.data.Errors', 'Ext.util.DelayedTask'],
98084
98085     /**
98086      * Creates new form.
98087      * @param {Ext.container.Container} owner The component that is the container for the form, usually a {@link Ext.form.Panel}
98088      * @param {Object} config Configuration options. These are normally specified in the config to the
98089      * {@link Ext.form.Panel} constructor, which passes them along to the BasicForm automatically.
98090      */
98091     constructor: function(owner, config) {
98092         var me = this,
98093             onItemAddOrRemove = me.onItemAddOrRemove;
98094
98095         /**
98096          * @property owner
98097          * @type Ext.container.Container
98098          * The container component to which this BasicForm is attached.
98099          */
98100         me.owner = owner;
98101
98102         // Listen for addition/removal of fields in the owner container
98103         me.mon(owner, {
98104             add: onItemAddOrRemove,
98105             remove: onItemAddOrRemove,
98106             scope: me
98107         });
98108
98109         Ext.apply(me, config);
98110
98111         // Normalize the paramOrder to an Array
98112         if (Ext.isString(me.paramOrder)) {
98113             me.paramOrder = me.paramOrder.split(/[\s,|]/);
98114         }
98115
98116         me.checkValidityTask = Ext.create('Ext.util.DelayedTask', me.checkValidity, me);
98117
98118         me.addEvents(
98119             /**
98120              * @event beforeaction
98121              * Fires before any action is performed. Return false to cancel the action.
98122              * @param {Ext.form.Basic} this
98123              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} to be performed
98124              */
98125             'beforeaction',
98126             /**
98127              * @event actionfailed
98128              * Fires when an action fails.
98129              * @param {Ext.form.Basic} this
98130              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that failed
98131              */
98132             'actionfailed',
98133             /**
98134              * @event actioncomplete
98135              * Fires when an action is completed.
98136              * @param {Ext.form.Basic} this
98137              * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that completed
98138              */
98139             'actioncomplete',
98140             /**
98141              * @event validitychange
98142              * Fires when the validity of the entire form changes.
98143              * @param {Ext.form.Basic} this
98144              * @param {Boolean} valid <tt>true</tt> if the form is now valid, <tt>false</tt> if it is now invalid.
98145              */
98146             'validitychange',
98147             /**
98148              * @event dirtychange
98149              * Fires when the dirty state of the entire form changes.
98150              * @param {Ext.form.Basic} this
98151              * @param {Boolean} dirty <tt>true</tt> if the form is now dirty, <tt>false</tt> if it is no longer dirty.
98152              */
98153             'dirtychange'
98154         );
98155         me.callParent();
98156     },
98157
98158     /**
98159      * Do any post constructor initialization
98160      * @private
98161      */
98162     initialize: function(){
98163         this.initialized = true;
98164         this.onValidityChange(!this.hasInvalidField());
98165     },
98166
98167     /**
98168      * @cfg {String} method
98169      * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
98170      */
98171
98172     /**
98173      * @cfg {Ext.data.reader.Reader} reader
98174      * An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to read
98175      * data when executing 'load' actions. This is optional as there is built-in
98176      * support for processing JSON responses.
98177      */
98178
98179     /**
98180      * @cfg {Ext.data.reader.Reader} errorReader
98181      * <p>An Ext.data.DataReader (e.g. {@link Ext.data.reader.Xml}) to be used to
98182      * read field error messages returned from 'submit' actions. This is optional
98183      * as there is built-in support for processing JSON responses.</p>
98184      * <p>The Records which provide messages for the invalid Fields must use the
98185      * Field name (or id) as the Record ID, and must contain a field called 'msg'
98186      * which contains the error message.</p>
98187      * <p>The errorReader does not have to be a full-blown implementation of a
98188      * Reader. It simply needs to implement a <tt>read(xhr)</tt> function
98189      * which returns an Array of Records in an object with the following
98190      * structure:</p><pre><code>
98191 {
98192     records: recordArray
98193 }
98194 </code></pre>
98195      */
98196
98197     /**
98198      * @cfg {String} url
98199      * The URL to use for form actions if one isn't supplied in the
98200      * {@link #doAction doAction} options.
98201      */
98202
98203     /**
98204      * @cfg {Object} baseParams
98205      * <p>Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.</p>
98206      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext.Object#toQueryString}.</p>
98207      */
98208
98209     /**
98210      * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
98211      */
98212     timeout: 30,
98213
98214     /**
98215      * @cfg {Object} api (Optional) If specified, load and submit actions will be handled
98216      * with {@link Ext.form.action.DirectLoad} and {@link Ext.form.action.DirectLoad}.
98217      * Methods which have been imported by {@link Ext.direct.Manager} can be specified here to load and submit
98218      * forms.
98219      * Such as the following:<pre><code>
98220 api: {
98221     load: App.ss.MyProfile.load,
98222     submit: App.ss.MyProfile.submit
98223 }
98224 </code></pre>
98225      * <p>Load actions can use <code>{@link #paramOrder}</code> or <code>{@link #paramsAsHash}</code>
98226      * to customize how the load method is invoked.
98227      * Submit actions will always use a standard form submit. The <tt>formHandler</tt> configuration must
98228      * be set on the associated server-side method which has been imported by {@link Ext.direct.Manager}.</p>
98229      */
98230
98231     /**
98232      * @cfg {String/String[]} paramOrder <p>A list of params to be executed server side.
98233      * Defaults to <tt>undefined</tt>. Only used for the <code>{@link #api}</code>
98234      * <code>load</code> configuration.</p>
98235      * <p>Specify the params in the order in which they must be executed on the
98236      * server-side as either (1) an Array of String values, or (2) a String of params
98237      * delimited by either whitespace, comma, or pipe. For example,
98238      * any of the following would be acceptable:</p><pre><code>
98239 paramOrder: ['param1','param2','param3']
98240 paramOrder: 'param1 param2 param3'
98241 paramOrder: 'param1,param2,param3'
98242 paramOrder: 'param1|param2|param'
98243      </code></pre>
98244      */
98245
98246     /**
98247      * @cfg {Boolean} paramsAsHash
98248      * Only used for the <code>{@link #api}</code>
98249      * <code>load</code> configuration. If <tt>true</tt>, parameters will be sent as a
98250      * single hash collection of named arguments. Providing a
98251      * <tt>{@link #paramOrder}</tt> nullifies this configuration.
98252      */
98253     paramsAsHash: false,
98254
98255     /**
98256      * @cfg {String} waitTitle
98257      * The default title to show for the waiting message box
98258      */
98259     waitTitle: 'Please Wait...',
98260
98261     /**
98262      * @cfg {Boolean} trackResetOnLoad
98263      * If set to true, {@link #reset}() resets to the last loaded or {@link #setValues}() data instead of
98264      * when the form was first created.
98265      */
98266     trackResetOnLoad: false,
98267
98268     /**
98269      * @cfg {Boolean} standardSubmit
98270      * If set to true, a standard HTML form submit is used instead of a XHR (Ajax) style form submission.
98271      * All of the field values, plus any additional params configured via {@link #baseParams}
98272      * and/or the `options` to {@link #submit}, will be included in the values submitted in the form.
98273      */
98274
98275     /**
98276      * @cfg {String/HTMLElement/Ext.Element} waitMsgTarget
98277      * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific
98278      * element by passing it or its id or mask the form itself by passing in true.
98279      */
98280
98281
98282     // Private
98283     wasDirty: false,
98284
98285
98286     /**
98287      * Destroys this object.
98288      */
98289     destroy: function() {
98290         this.clearListeners();
98291         this.checkValidityTask.cancel();
98292     },
98293
98294     /**
98295      * @private
98296      * Handle addition or removal of descendant items. Invalidates the cached list of fields
98297      * so that {@link #getFields} will do a fresh query next time it is called. Also adds listeners
98298      * for state change events on added fields, and tracks components with formBind=true.
98299      */
98300     onItemAddOrRemove: function(parent, child) {
98301         var me = this,
98302             isAdding = !!child.ownerCt,
98303             isContainer = child.isContainer;
98304
98305         function handleField(field) {
98306             // Listen for state change events on fields
98307             me[isAdding ? 'mon' : 'mun'](field, {
98308                 validitychange: me.checkValidity,
98309                 dirtychange: me.checkDirty,
98310                 scope: me,
98311                 buffer: 100 //batch up sequential calls to avoid excessive full-form validation
98312             });
98313             // Flush the cached list of fields
98314             delete me._fields;
98315         }
98316
98317         if (child.isFormField) {
98318             handleField(child);
98319         } else if (isContainer) {
98320             // Walk down
98321             if (child.isDestroyed) {
98322                 // the container is destroyed, this means we may have child fields, so here
98323                 // we just invalidate all the fields to be sure.
98324                 delete me._fields;
98325             } else {
98326                 Ext.Array.forEach(child.query('[isFormField]'), handleField);
98327             }
98328         }
98329
98330         // Flush the cached list of formBind components
98331         delete this._boundItems;
98332
98333         // Check form bind, but only after initial add. Batch it to prevent excessive validation
98334         // calls when many fields are being added at once.
98335         if (me.initialized) {
98336             me.checkValidityTask.delay(10);
98337         }
98338     },
98339
98340     /**
98341      * Return all the {@link Ext.form.field.Field} components in the owner container.
98342      * @return {Ext.util.MixedCollection} Collection of the Field objects
98343      */
98344     getFields: function() {
98345         var fields = this._fields;
98346         if (!fields) {
98347             fields = this._fields = Ext.create('Ext.util.MixedCollection');
98348             fields.addAll(this.owner.query('[isFormField]'));
98349         }
98350         return fields;
98351     },
98352
98353     /**
98354      * @private
98355      * Finds and returns the set of all items bound to fields inside this form
98356      * @return {Ext.util.MixedCollection} The set of all bound form field items
98357      */
98358     getBoundItems: function() {
98359         var boundItems = this._boundItems;
98360         
98361         if (!boundItems || boundItems.getCount() === 0) {
98362             boundItems = this._boundItems = Ext.create('Ext.util.MixedCollection');
98363             boundItems.addAll(this.owner.query('[formBind]'));
98364         }
98365         
98366         return boundItems;
98367     },
98368
98369     /**
98370      * Returns true if the form contains any invalid fields. No fields will be marked as invalid
98371      * as a result of calling this; to trigger marking of fields use {@link #isValid} instead.
98372      */
98373     hasInvalidField: function() {
98374         return !!this.getFields().findBy(function(field) {
98375             var preventMark = field.preventMark,
98376                 isValid;
98377             field.preventMark = true;
98378             isValid = field.isValid();
98379             field.preventMark = preventMark;
98380             return !isValid;
98381         });
98382     },
98383
98384     /**
98385      * Returns true if client-side validation on the form is successful. Any invalid fields will be
98386      * marked as invalid. If you only want to determine overall form validity without marking anything,
98387      * use {@link #hasInvalidField} instead.
98388      * @return Boolean
98389      */
98390     isValid: function() {
98391         var me = this,
98392             invalid;
98393         me.batchLayouts(function() {
98394             invalid = me.getFields().filterBy(function(field) {
98395                 return !field.validate();
98396             });
98397         });
98398         return invalid.length < 1;
98399     },
98400
98401     /**
98402      * Check whether the validity of the entire form has changed since it was last checked, and
98403      * if so fire the {@link #validitychange validitychange} event. This is automatically invoked
98404      * when an individual field's validity changes.
98405      */
98406     checkValidity: function() {
98407         var me = this,
98408             valid = !me.hasInvalidField();
98409         if (valid !== me.wasValid) {
98410             me.onValidityChange(valid);
98411             me.fireEvent('validitychange', me, valid);
98412             me.wasValid = valid;
98413         }
98414     },
98415
98416     /**
98417      * @private
98418      * Handle changes in the form's validity. If there are any sub components with
98419      * formBind=true then they are enabled/disabled based on the new validity.
98420      * @param {Boolean} valid
98421      */
98422     onValidityChange: function(valid) {
98423         var boundItems = this.getBoundItems();
98424         if (boundItems) {
98425             boundItems.each(function(cmp) {
98426                 if (cmp.disabled === valid) {
98427                     cmp.setDisabled(!valid);
98428                 }
98429             });
98430         }
98431     },
98432
98433     /**
98434      * <p>Returns true if any fields in this form have changed from their original values.</p>
98435      * <p>Note that if this BasicForm was configured with {@link #trackResetOnLoad} then the
98436      * Fields' <em>original values</em> are updated when the values are loaded by {@link #setValues}
98437      * or {@link #loadRecord}.</p>
98438      * @return Boolean
98439      */
98440     isDirty: function() {
98441         return !!this.getFields().findBy(function(f) {
98442             return f.isDirty();
98443         });
98444     },
98445
98446     /**
98447      * Check whether the dirty state of the entire form has changed since it was last checked, and
98448      * if so fire the {@link #dirtychange dirtychange} event. This is automatically invoked
98449      * when an individual field's dirty state changes.
98450      */
98451     checkDirty: function() {
98452         var dirty = this.isDirty();
98453         if (dirty !== this.wasDirty) {
98454             this.fireEvent('dirtychange', this, dirty);
98455             this.wasDirty = dirty;
98456         }
98457     },
98458
98459     /**
98460      * <p>Returns true if the form contains a file upload field. This is used to determine the
98461      * method for submitting the form: File uploads are not performed using normal 'Ajax' techniques,
98462      * that is they are <b>not</b> performed using XMLHttpRequests. Instead a hidden <tt>&lt;form></tt>
98463      * element containing all the fields is created temporarily and submitted with its
98464      * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
98465      * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
98466      * but removed after the return data has been gathered.</p>
98467      * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
98468      * server is using JSON to send the return object, then the
98469      * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
98470      * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
98471      * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
98472      * "&lt;" as "&amp;lt;", "&amp;" as "&amp;amp;" etc.</p>
98473      * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
98474      * is created containing a <tt>responseText</tt> property in order to conform to the
98475      * requirements of event handlers and callbacks.</p>
98476      * <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>
98477      * and some server technologies (notably JEE) may require some custom processing in order to
98478      * retrieve parameter names and parameter values from the packet content.</p>
98479      * @return Boolean
98480      */
98481     hasUpload: function() {
98482         return !!this.getFields().findBy(function(f) {
98483             return f.isFileUpload();
98484         });
98485     },
98486
98487     /**
98488      * Performs a predefined action (an implementation of {@link Ext.form.action.Action})
98489      * to perform application-specific processing.
98490      * @param {String/Ext.form.action.Action} action The name of the predefined action type,
98491      * or instance of {@link Ext.form.action.Action} to perform.
98492      * @param {Object} options (optional) The options to pass to the {@link Ext.form.action.Action}
98493      * that will get created, if the <tt>action</tt> argument is a String.
98494      * <p>All of the config options listed below are supported by both the
98495      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
98496      * actions unless otherwise noted (custom actions could also accept
98497      * other config options):</p><ul>
98498      *
98499      * <li><b>url</b> : String<div class="sub-desc">The url for the action (defaults
98500      * to the form's {@link #url}.)</div></li>
98501      *
98502      * <li><b>method</b> : String<div class="sub-desc">The form method to use (defaults
98503      * to the form's method, or POST if not defined)</div></li>
98504      *
98505      * <li><b>params</b> : String/Object<div class="sub-desc"><p>The params to pass
98506      * (defaults to the form's baseParams, or none if not defined)</p>
98507      * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.</p></div></li>
98508      *
98509      * <li><b>headers</b> : Object<div class="sub-desc">Request headers to set for the action.</div></li>
98510      *
98511      * <li><b>success</b> : Function<div class="sub-desc">The callback that will
98512      * be invoked after a successful response (see top of
98513      * {@link Ext.form.action.Submit submit} and {@link Ext.form.action.Load load}
98514      * for a description of what constitutes a successful response).
98515      * The function is passed the following parameters:<ul>
98516      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
98517      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
98518      * <div class="sub-desc">The action object contains these properties of interest:<ul>
98519      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
98520      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
98521      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
98522      * </ul></div></li></ul></div></li>
98523      *
98524      * <li><b>failure</b> : Function<div class="sub-desc">The callback that will be invoked after a
98525      * failed transaction attempt. The function is passed the following parameters:<ul>
98526      * <li><tt>form</tt> : The {@link Ext.form.Basic} that requested the action.</li>
98527      * <li><tt>action</tt> : The {@link Ext.form.action.Action Action} object which performed the operation.
98528      * <div class="sub-desc">The action object contains these properties of interest:<ul>
98529      * <li><tt>{@link Ext.form.action.Action#failureType failureType}</tt></li>
98530      * <li><tt>{@link Ext.form.action.Action#response response}</tt></li>
98531      * <li><tt>{@link Ext.form.action.Action#result result}</tt> : interrogate for custom postprocessing</li>
98532      * <li><tt>{@link Ext.form.action.Action#type type}</tt></li>
98533      * </ul></div></li></ul></div></li>
98534      *
98535      * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the
98536      * callback functions (The <tt>this</tt> reference for the callback functions).</div></li>
98537      *
98538      * <li><b>clientValidation</b> : Boolean<div class="sub-desc">Submit Action only.
98539      * Determines whether a Form's fields are validated in a final call to
98540      * {@link Ext.form.Basic#isValid isValid} prior to submission. Set to <tt>false</tt>
98541      * to prevent this. If undefined, pre-submission field validation is performed.</div></li></ul>
98542      *
98543      * @return {Ext.form.Basic} this
98544      */
98545     doAction: function(action, options) {
98546         if (Ext.isString(action)) {
98547             action = Ext.ClassManager.instantiateByAlias('formaction.' + action, Ext.apply({}, options, {form: this}));
98548         }
98549         if (this.fireEvent('beforeaction', this, action) !== false) {
98550             this.beforeAction(action);
98551             Ext.defer(action.run, 100, action);
98552         }
98553         return this;
98554     },
98555
98556     /**
98557      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Submit submit action}. This will use the
98558      * {@link Ext.form.action.Submit AJAX submit action} by default. If the {@link #standardSubmit} config is
98559      * enabled it will use a standard form element to submit, or if the {@link #api} config is present it will
98560      * use the {@link Ext.form.action.DirectLoad Ext.direct.Direct submit action}.
98561      * @param {Object} options The options to pass to the action (see {@link #doAction} for details).<br>
98562      * <p>The following code:</p><pre><code>
98563 myFormPanel.getForm().submit({
98564     clientValidation: true,
98565     url: 'updateConsignment.php',
98566     params: {
98567         newStatus: 'delivered'
98568     },
98569     success: function(form, action) {
98570        Ext.Msg.alert('Success', action.result.msg);
98571     },
98572     failure: function(form, action) {
98573         switch (action.failureType) {
98574             case Ext.form.action.Action.CLIENT_INVALID:
98575                 Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
98576                 break;
98577             case Ext.form.action.Action.CONNECT_FAILURE:
98578                 Ext.Msg.alert('Failure', 'Ajax communication failed');
98579                 break;
98580             case Ext.form.action.Action.SERVER_INVALID:
98581                Ext.Msg.alert('Failure', action.result.msg);
98582        }
98583     }
98584 });
98585 </code></pre>
98586      * would process the following server response for a successful submission:<pre><code>
98587 {
98588     "success":true, // note this is Boolean, not string
98589     "msg":"Consignment updated"
98590 }
98591 </code></pre>
98592      * and the following server response for a failed submission:<pre><code>
98593 {
98594     "success":false, // note this is Boolean, not string
98595     "msg":"You do not have permission to perform this operation"
98596 }
98597 </code></pre>
98598      * @return {Ext.form.Basic} this
98599      */
98600     submit: function(options) {
98601         return this.doAction(this.standardSubmit ? 'standardsubmit' : this.api ? 'directsubmit' : 'submit', options);
98602     },
98603
98604     /**
98605      * Shortcut to {@link #doAction do} a {@link Ext.form.action.Load load action}.
98606      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
98607      * @return {Ext.form.Basic} this
98608      */
98609     load: function(options) {
98610         return this.doAction(this.api ? 'directload' : 'load', options);
98611     },
98612
98613     /**
98614      * Persists the values in this form into the passed {@link Ext.data.Model} object in a beginEdit/endEdit block.
98615      * @param {Ext.data.Model} record The record to edit
98616      * @return {Ext.form.Basic} this
98617      */
98618     updateRecord: function(record) {
98619         var fields = record.fields,
98620             values = this.getFieldValues(),
98621             name,
98622             obj = {};
98623
98624         fields.each(function(f) {
98625             name = f.name;
98626             if (name in values) {
98627                 obj[name] = values[name];
98628             }
98629         });
98630
98631         record.beginEdit();
98632         record.set(obj);
98633         record.endEdit();
98634
98635         return this;
98636     },
98637
98638     /**
98639      * Loads an {@link Ext.data.Model} into this form by calling {@link #setValues} with the
98640      * {@link Ext.data.Model#raw record data}.
98641      * See also {@link #trackResetOnLoad}.
98642      * @param {Ext.data.Model} record The record to load
98643      * @return {Ext.form.Basic} this
98644      */
98645     loadRecord: function(record) {
98646         this._record = record;
98647         return this.setValues(record.data);
98648     },
98649
98650     /**
98651      * Returns the last Ext.data.Model instance that was loaded via {@link #loadRecord}
98652      * @return {Ext.data.Model} The record
98653      */
98654     getRecord: function() {
98655         return this._record;
98656     },
98657
98658     /**
98659      * @private
98660      * Called before an action is performed via {@link #doAction}.
98661      * @param {Ext.form.action.Action} action The Action instance that was invoked
98662      */
98663     beforeAction: function(action) {
98664         var waitMsg = action.waitMsg,
98665             maskCls = Ext.baseCSSPrefix + 'mask-loading',
98666             waitMsgTarget;
98667
98668         // Call HtmlEditor's syncValue before actions
98669         this.getFields().each(function(f) {
98670             if (f.isFormField && f.syncValue) {
98671                 f.syncValue();
98672             }
98673         });
98674
98675         if (waitMsg) {
98676             waitMsgTarget = this.waitMsgTarget;
98677             if (waitMsgTarget === true) {
98678                 this.owner.el.mask(waitMsg, maskCls);
98679             } else if (waitMsgTarget) {
98680                 waitMsgTarget = this.waitMsgTarget = Ext.get(waitMsgTarget);
98681                 waitMsgTarget.mask(waitMsg, maskCls);
98682             } else {
98683                 Ext.MessageBox.wait(waitMsg, action.waitTitle || this.waitTitle);
98684             }
98685         }
98686     },
98687
98688     /**
98689      * @private
98690      * Called after an action is performed via {@link #doAction}.
98691      * @param {Ext.form.action.Action} action The Action instance that was invoked
98692      * @param {Boolean} success True if the action completed successfully, false, otherwise.
98693      */
98694     afterAction: function(action, success) {
98695         if (action.waitMsg) {
98696             var MessageBox = Ext.MessageBox,
98697                 waitMsgTarget = this.waitMsgTarget;
98698             if (waitMsgTarget === true) {
98699                 this.owner.el.unmask();
98700             } else if (waitMsgTarget) {
98701                 waitMsgTarget.unmask();
98702             } else {
98703                 MessageBox.updateProgress(1);
98704                 MessageBox.hide();
98705             }
98706         }
98707         if (success) {
98708             if (action.reset) {
98709                 this.reset();
98710             }
98711             Ext.callback(action.success, action.scope || action, [this, action]);
98712             this.fireEvent('actioncomplete', this, action);
98713         } else {
98714             Ext.callback(action.failure, action.scope || action, [this, action]);
98715             this.fireEvent('actionfailed', this, action);
98716         }
98717     },
98718
98719
98720     /**
98721      * Find a specific {@link Ext.form.field.Field} in this form by id or name.
98722      * @param {String} id The value to search for (specify either a {@link Ext.Component#id id} or
98723      * {@link Ext.form.field.Field#getName name or hiddenName}).
98724      * @return Ext.form.field.Field The first matching field, or <tt>null</tt> if none was found.
98725      */
98726     findField: function(id) {
98727         return this.getFields().findBy(function(f) {
98728             return f.id === id || f.getName() === id;
98729         });
98730     },
98731
98732
98733     /**
98734      * Mark fields in this form invalid in bulk.
98735      * @param {Object/Object[]/Ext.data.Errors} errors
98736      * Either an array in the form <code>[{id:'fieldId', msg:'The message'}, ...]</code>,
98737      * an object hash of <code>{id: msg, id2: msg2}</code>, or a {@link Ext.data.Errors} object.
98738      * @return {Ext.form.Basic} this
98739      */
98740     markInvalid: function(errors) {
98741         var me = this;
98742
98743         function mark(fieldId, msg) {
98744             var field = me.findField(fieldId);
98745             if (field) {
98746                 field.markInvalid(msg);
98747             }
98748         }
98749
98750         if (Ext.isArray(errors)) {
98751             Ext.each(errors, function(err) {
98752                 mark(err.id, err.msg);
98753             });
98754         }
98755         else if (errors instanceof Ext.data.Errors) {
98756             errors.each(function(err) {
98757                 mark(err.field, err.message);
98758             });
98759         }
98760         else {
98761             Ext.iterate(errors, mark);
98762         }
98763         return this;
98764     },
98765
98766     /**
98767      * Set values for fields in this form in bulk.
98768      * @param {Object/Object[]} values Either an array in the form:<pre><code>
98769 [{id:'clientName', value:'Fred. Olsen Lines'},
98770  {id:'portOfLoading', value:'FXT'},
98771  {id:'portOfDischarge', value:'OSL'} ]</code></pre>
98772      * or an object hash of the form:<pre><code>
98773 {
98774     clientName: 'Fred. Olsen Lines',
98775     portOfLoading: 'FXT',
98776     portOfDischarge: 'OSL'
98777 }</code></pre>
98778      * @return {Ext.form.Basic} this
98779      */
98780     setValues: function(values) {
98781         var me = this;
98782
98783         function setVal(fieldId, val) {
98784             var field = me.findField(fieldId);
98785             if (field) {
98786                 field.setValue(val);
98787                 if (me.trackResetOnLoad) {
98788                     field.resetOriginalValue();
98789                 }
98790             }
98791         }
98792
98793         if (Ext.isArray(values)) {
98794             // array of objects
98795             Ext.each(values, function(val) {
98796                 setVal(val.id, val.value);
98797             });
98798         } else {
98799             // object hash
98800             Ext.iterate(values, setVal);
98801         }
98802         return this;
98803     },
98804
98805     /**
98806      * Retrieves the fields in the form as a set of key/value pairs, using their
98807      * {@link Ext.form.field.Field#getSubmitData getSubmitData()} method to collect the values.
98808      * If multiple fields return values under the same name those values will be combined into an Array.
98809      * This is similar to {@link #getFieldValues} except that this method collects only String values for
98810      * submission, while getFieldValues collects type-specific data values (e.g. Date objects for date fields.)
98811      * @param {Boolean} asString (optional) If true, will return the key/value collection as a single
98812      * URL-encoded param string. Defaults to false.
98813      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
98814      * Defaults to false.
98815      * @param {Boolean} includeEmptyText (optional) If true, the configured emptyText of empty fields will be used.
98816      * Defaults to false.
98817      * @return {String/Object}
98818      */
98819     getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues) {
98820         var values = {};
98821
98822         this.getFields().each(function(field) {
98823             if (!dirtyOnly || field.isDirty()) {
98824                 var data = field[useDataValues ? 'getModelData' : 'getSubmitData'](includeEmptyText);
98825                 if (Ext.isObject(data)) {
98826                     Ext.iterate(data, function(name, val) {
98827                         if (includeEmptyText && val === '') {
98828                             val = field.emptyText || '';
98829                         }
98830                         if (name in values) {
98831                             var bucket = values[name],
98832                                 isArray = Ext.isArray;
98833                             if (!isArray(bucket)) {
98834                                 bucket = values[name] = [bucket];
98835                             }
98836                             if (isArray(val)) {
98837                                 values[name] = bucket.concat(val);
98838                             } else {
98839                                 bucket.push(val);
98840                             }
98841                         } else {
98842                             values[name] = val;
98843                         }
98844                     });
98845                 }
98846             }
98847         });
98848
98849         if (asString) {
98850             values = Ext.Object.toQueryString(values);
98851         }
98852         return values;
98853     },
98854
98855     /**
98856      * Retrieves the fields in the form as a set of key/value pairs, using their
98857      * {@link Ext.form.field.Field#getModelData getModelData()} method to collect the values.
98858      * If multiple fields return values under the same name those values will be combined into an Array.
98859      * This is similar to {@link #getValues} except that this method collects type-specific data values
98860      * (e.g. Date objects for date fields) while getValues returns only String values for submission.
98861      * @param {Boolean} dirtyOnly (optional) If true, only fields that are dirty will be included in the result.
98862      * Defaults to false.
98863      * @return {Object}
98864      */
98865     getFieldValues: function(dirtyOnly) {
98866         return this.getValues(false, dirtyOnly, false, true);
98867     },
98868
98869     /**
98870      * Clears all invalid field messages in this form.
98871      * @return {Ext.form.Basic} this
98872      */
98873     clearInvalid: function() {
98874         var me = this;
98875         me.batchLayouts(function() {
98876             me.getFields().each(function(f) {
98877                 f.clearInvalid();
98878             });
98879         });
98880         return me;
98881     },
98882
98883     /**
98884      * Resets all fields in this form.
98885      * @return {Ext.form.Basic} this
98886      */
98887     reset: function() {
98888         var me = this;
98889         me.batchLayouts(function() {
98890             me.getFields().each(function(f) {
98891                 f.reset();
98892             });
98893         });
98894         return me;
98895     },
98896
98897     /**
98898      * Calls {@link Ext#apply Ext.apply} for all fields in this form with the passed object.
98899      * @param {Object} obj The object to be applied
98900      * @return {Ext.form.Basic} this
98901      */
98902     applyToFields: function(obj) {
98903         this.getFields().each(function(f) {
98904             Ext.apply(f, obj);
98905         });
98906         return this;
98907     },
98908
98909     /**
98910      * Calls {@link Ext#applyIf Ext.applyIf} for all field in this form with the passed object.
98911      * @param {Object} obj The object to be applied
98912      * @return {Ext.form.Basic} this
98913      */
98914     applyIfToFields: function(obj) {
98915         this.getFields().each(function(f) {
98916             Ext.applyIf(f, obj);
98917         });
98918         return this;
98919     },
98920
98921     /**
98922      * @private
98923      * Utility wrapper that suspends layouts of all field parent containers for the duration of a given
98924      * function. Used during full-form validation and resets to prevent huge numbers of layouts.
98925      * @param {Function} fn
98926      */
98927     batchLayouts: function(fn) {
98928         var me = this,
98929             suspended = new Ext.util.HashMap();
98930
98931         // Temporarily suspend layout on each field's immediate owner so we don't get a huge layout cascade
98932         me.getFields().each(function(field) {
98933             var ownerCt = field.ownerCt;
98934             if (!suspended.contains(ownerCt)) {
98935                 suspended.add(ownerCt);
98936                 ownerCt.oldSuspendLayout = ownerCt.suspendLayout;
98937                 ownerCt.suspendLayout = true;
98938             }
98939         });
98940
98941         // Invoke the function
98942         fn();
98943
98944         // Un-suspend the container layouts
98945         suspended.each(function(id, ct) {
98946             ct.suspendLayout = ct.oldSuspendLayout;
98947             delete ct.oldSuspendLayout;
98948         });
98949
98950         // Trigger a single layout
98951         me.owner.doComponentLayout();
98952     }
98953 });
98954
98955 /**
98956  * @class Ext.form.FieldAncestor
98957
98958 A mixin for {@link Ext.container.Container} components that are likely to have form fields in their
98959 items subtree. Adds the following capabilities:
98960
98961 - Methods for handling the addition and removal of {@link Ext.form.Labelable} and {@link Ext.form.field.Field}
98962   instances at any depth within the container.
98963 - Events ({@link #fieldvaliditychange} and {@link #fielderrorchange}) for handling changes to the state
98964   of individual fields at the container level.
98965 - Automatic application of {@link #fieldDefaults} config properties to each field added within the
98966   container, to facilitate uniform configuration of all fields.
98967
98968 This mixin is primarily for internal use by {@link Ext.form.Panel} and {@link Ext.form.FieldContainer},
98969 and should not normally need to be used directly.
98970
98971  * @markdown
98972  * @docauthor Jason Johnston <jason@sencha.com>
98973  */
98974 Ext.define('Ext.form.FieldAncestor', {
98975
98976     /**
98977      * @cfg {Object} fieldDefaults
98978      * <p>If specified, the properties in this object are used as default config values for each
98979      * {@link Ext.form.Labelable} instance (e.g. {@link Ext.form.field.Base} or {@link Ext.form.FieldContainer})
98980      * that is added as a descendant of this container. Corresponding values specified in an individual field's
98981      * own configuration, or from the {@link Ext.container.Container#defaults defaults config} of its parent container,
98982      * will take precedence. See the documentation for {@link Ext.form.Labelable} to see what config
98983      * options may be specified in the <tt>fieldDefaults</tt>.</p>
98984      * <p>Example:</p>
98985      * <pre><code>new Ext.form.Panel({
98986     fieldDefaults: {
98987         labelAlign: 'left',
98988         labelWidth: 100
98989     },
98990     items: [{
98991         xtype: 'fieldset',
98992         defaults: {
98993             labelAlign: 'top'
98994         },
98995         items: [{
98996             name: 'field1'
98997         }, {
98998             name: 'field2'
98999         }]
99000     }, {
99001         xtype: 'fieldset',
99002         items: [{
99003             name: 'field3',
99004             labelWidth: 150
99005         }, {
99006             name: 'field4'
99007         }]
99008     }]
99009 });</code></pre>
99010      * <p>In this example, field1 and field2 will get labelAlign:'top' (from the fieldset's <tt>defaults</tt>)
99011      * and labelWidth:100 (from <tt>fieldDefaults</tt>), field3 and field4 will both get labelAlign:'left' (from
99012      * <tt>fieldDefaults</tt> and field3 will use the labelWidth:150 from its own config.</p>
99013      */
99014
99015
99016     /**
99017      * @protected Initializes the FieldAncestor's state; this must be called from the initComponent method
99018      * of any components importing this mixin.
99019      */
99020     initFieldAncestor: function() {
99021         var me = this,
99022             onSubtreeChange = me.onFieldAncestorSubtreeChange;
99023
99024         me.addEvents(
99025             /**
99026              * @event fieldvaliditychange
99027              * Fires when the validity state of any one of the {@link Ext.form.field.Field} instances within this
99028              * container changes.
99029              * @param {Ext.form.FieldAncestor} this
99030              * @param {Ext.form.Labelable} The Field instance whose validity changed
99031              * @param {String} isValid The field's new validity state
99032              */
99033             'fieldvaliditychange',
99034
99035             /**
99036              * @event fielderrorchange
99037              * Fires when the active error message is changed for any one of the {@link Ext.form.Labelable}
99038              * instances within this container.
99039              * @param {Ext.form.FieldAncestor} this
99040              * @param {Ext.form.Labelable} The Labelable instance whose active error was changed
99041              * @param {String} error The active error message
99042              */
99043             'fielderrorchange'
99044         );
99045
99046         // Catch addition and removal of descendant fields
99047         me.on('add', onSubtreeChange, me);
99048         me.on('remove', onSubtreeChange, me);
99049
99050         me.initFieldDefaults();
99051     },
99052
99053     /**
99054      * @private Initialize the {@link #fieldDefaults} object
99055      */
99056     initFieldDefaults: function() {
99057         if (!this.fieldDefaults) {
99058             this.fieldDefaults = {};
99059         }
99060     },
99061
99062     /**
99063      * @private
99064      * Handle the addition and removal of components in the FieldAncestor component's child tree.
99065      */
99066     onFieldAncestorSubtreeChange: function(parent, child) {
99067         var me = this,
99068             isAdding = !!child.ownerCt;
99069
99070         function handleCmp(cmp) {
99071             var isLabelable = cmp.isFieldLabelable,
99072                 isField = cmp.isFormField;
99073             if (isLabelable || isField) {
99074                 if (isLabelable) {
99075                     me['onLabelable' + (isAdding ? 'Added' : 'Removed')](cmp);
99076                 }
99077                 if (isField) {
99078                     me['onField' + (isAdding ? 'Added' : 'Removed')](cmp);
99079                 }
99080             }
99081             else if (cmp.isContainer) {
99082                 Ext.Array.forEach(cmp.getRefItems(), handleCmp);
99083             }
99084         }
99085         handleCmp(child);
99086     },
99087
99088     /**
99089      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
99090      * @param {Ext.form.Labelable} labelable The instance that was added
99091      */
99092     onLabelableAdded: function(labelable) {
99093         var me = this;
99094
99095         // buffer slightly to avoid excessive firing while sub-fields are changing en masse
99096         me.mon(labelable, 'errorchange', me.handleFieldErrorChange, me, {buffer: 10});
99097
99098         labelable.setFieldDefaults(me.fieldDefaults);
99099     },
99100
99101     /**
99102      * @protected Called when a {@link Ext.form.field.Field} instance is added to the container's subtree.
99103      * @param {Ext.form.field.Field} field The field which was added
99104      */
99105     onFieldAdded: function(field) {
99106         var me = this;
99107         me.mon(field, 'validitychange', me.handleFieldValidityChange, me);
99108     },
99109
99110     /**
99111      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
99112      * @param {Ext.form.Labelable} labelable The instance that was removed
99113      */
99114     onLabelableRemoved: function(labelable) {
99115         var me = this;
99116         me.mun(labelable, 'errorchange', me.handleFieldErrorChange, me);
99117     },
99118
99119     /**
99120      * @protected Called when a {@link Ext.form.field.Field} instance is removed from the container's subtree.
99121      * @param {Ext.form.field.Field} field The field which was removed
99122      */
99123     onFieldRemoved: function(field) {
99124         var me = this;
99125         me.mun(field, 'validitychange', me.handleFieldValidityChange, me);
99126     },
99127
99128     /**
99129      * @private Handle validitychange events on sub-fields; invoke the aggregated event and method
99130      */
99131     handleFieldValidityChange: function(field, isValid) {
99132         var me = this;
99133         me.fireEvent('fieldvaliditychange', me, field, isValid);
99134         me.onFieldValidityChange();
99135     },
99136
99137     /**
99138      * @private Handle errorchange events on sub-fields; invoke the aggregated event and method
99139      */
99140     handleFieldErrorChange: function(labelable, activeError) {
99141         var me = this;
99142         me.fireEvent('fielderrorchange', me, labelable, activeError);
99143         me.onFieldErrorChange();
99144     },
99145
99146     /**
99147      * @protected Fired when the validity of any field within the container changes.
99148      * @param {Ext.form.field.Field} The sub-field whose validity changed
99149      * @param {String} The new validity state
99150      */
99151     onFieldValidityChange: Ext.emptyFn,
99152
99153     /**
99154      * @protected Fired when the error message of any field within the container changes.
99155      * @param {Ext.form.Labelable} The sub-field whose active error changed
99156      * @param {String} The new active error message
99157      */
99158     onFieldErrorChange: Ext.emptyFn
99159
99160 });
99161 /**
99162  * @class Ext.layout.container.CheckboxGroup
99163  * @extends Ext.layout.container.Container
99164  * <p>This layout implements the column arrangement for {@link Ext.form.CheckboxGroup} and {@link Ext.form.RadioGroup}.
99165  * It groups the component's sub-items into columns based on the component's
99166  * {@link Ext.form.CheckboxGroup#columns columns} and {@link Ext.form.CheckboxGroup#vertical} config properties.</p>
99167  *
99168  */
99169 Ext.define('Ext.layout.container.CheckboxGroup', {
99170     extend: 'Ext.layout.container.Container',
99171     alias: ['layout.checkboxgroup'],
99172
99173
99174     onLayout: function() {
99175         var numCols = this.getColCount(),
99176             shadowCt = this.getShadowCt(),
99177             owner = this.owner,
99178             items = owner.items,
99179             shadowItems = shadowCt.items,
99180             numItems = items.length,
99181             colIndex = 0,
99182             i, numRows;
99183
99184         // Distribute the items into the appropriate column containers. We add directly to the
99185         // containers' items collection rather than calling container.add(), because we need the
99186         // checkboxes to maintain their original ownerCt. The distribution is done on each layout
99187         // in case items have been added, removed, or reordered.
99188
99189         shadowItems.each(function(col) {
99190             col.items.clear();
99191         });
99192
99193         // If columns="auto", then the number of required columns may change as checkboxes are added/removed
99194         // from the CheckboxGroup; adjust to match.
99195         while (shadowItems.length > numCols) {
99196             shadowCt.remove(shadowItems.last());
99197         }
99198         while (shadowItems.length < numCols) {
99199             shadowCt.add({
99200                 xtype: 'container',
99201                 cls: owner.groupCls,
99202                 flex: 1
99203             });
99204         }
99205
99206         if (owner.vertical) {
99207             numRows = Math.ceil(numItems / numCols);
99208             for (i = 0; i < numItems; i++) {
99209                 if (i > 0 && i % numRows === 0) {
99210                     colIndex++;
99211                 }
99212                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
99213             }
99214         } else {
99215             for (i = 0; i < numItems; i++) {
99216                 colIndex = i % numCols;
99217                 shadowItems.getAt(colIndex).items.add(items.getAt(i));
99218             }
99219         }
99220
99221         if (!shadowCt.rendered) {
99222             shadowCt.render(this.getRenderTarget());
99223         } else {
99224             // Ensure all items are rendered in the correct place in the correct column - this won't
99225             // get done by the column containers themselves if their dimensions are not changing.
99226             shadowItems.each(function(col) {
99227                 var layout = col.getLayout();
99228                 layout.renderItems(layout.getLayoutItems(), layout.getRenderTarget());
99229             });
99230         }
99231
99232         shadowCt.doComponentLayout();
99233     },
99234
99235
99236     // We don't want to render any items to the owner directly, that gets handled by each column's own layout
99237     renderItems: Ext.emptyFn,
99238
99239
99240     /**
99241      * @private
99242      * Creates and returns the shadow hbox container that will be used to arrange the owner's items
99243      * into columns.
99244      */
99245     getShadowCt: function() {
99246         var me = this,
99247             shadowCt = me.shadowCt,
99248             owner, items, item, columns, columnsIsArray, numCols, i;
99249
99250         if (!shadowCt) {
99251             // Create the column containers based on the owner's 'columns' config
99252             owner = me.owner;
99253             columns = owner.columns;
99254             columnsIsArray = Ext.isArray(columns);
99255             numCols = me.getColCount();
99256             items = [];
99257             for(i = 0; i < numCols; i++) {
99258                 item = {
99259                     xtype: 'container',
99260                     cls: owner.groupCls
99261                 };
99262                 if (columnsIsArray) {
99263                     // Array can contain mixture of whole numbers, used as fixed pixel widths, and fractional
99264                     // numbers, used as relative flex values.
99265                     if (columns[i] < 1) {
99266                         item.flex = columns[i];
99267                     } else {
99268                         item.width = columns[i];
99269                     }
99270                 }
99271                 else {
99272                     // All columns the same width
99273                     item.flex = 1;
99274                 }
99275                 items.push(item);
99276             }
99277
99278             // Create the shadow container; delay rendering until after items are added to the columns
99279             shadowCt = me.shadowCt = Ext.createWidget('container', {
99280                 layout: 'hbox',
99281                 items: items,
99282                 ownerCt: owner
99283             });
99284         }
99285         
99286         return shadowCt;
99287     },
99288
99289
99290     /**
99291      * @private Get the number of columns in the checkbox group
99292      */
99293     getColCount: function() {
99294         var owner = this.owner,
99295             colsCfg = owner.columns;
99296         return Ext.isArray(colsCfg) ? colsCfg.length : (Ext.isNumber(colsCfg) ? colsCfg : owner.items.length);
99297     }
99298
99299 });
99300
99301 /**
99302  * FieldContainer is a derivation of {@link Ext.container.Container Container} that implements the
99303  * {@link Ext.form.Labelable Labelable} mixin. This allows it to be configured so that it is rendered with
99304  * a {@link #fieldLabel field label} and optional {@link #msgTarget error message} around its sub-items.
99305  * This is useful for arranging a group of fields or other components within a single item in a form, so
99306  * that it lines up nicely with other fields. A common use is for grouping a set of related fields under
99307  * a single label in a form.
99308  * 
99309  * The container's configured {@link #items} will be layed out within the field body area according to the
99310  * configured {@link #layout} type. The default layout is `'autocontainer'`.
99311  * 
99312  * Like regular fields, FieldContainer can inherit its decoration configuration from the
99313  * {@link Ext.form.Panel#fieldDefaults fieldDefaults} of an enclosing FormPanel. In addition,
99314  * FieldContainer itself can pass {@link #fieldDefaults} to any {@link Ext.form.Labelable fields}
99315  * it may itself contain.
99316  * 
99317  * If you are grouping a set of {@link Ext.form.field.Checkbox Checkbox} or {@link Ext.form.field.Radio Radio}
99318  * fields in a single labeled container, consider using a {@link Ext.form.CheckboxGroup}
99319  * or {@link Ext.form.RadioGroup} instead as they are specialized for handling those types.
99320  *
99321  * # Example
99322  * 
99323  *     @example
99324  *     Ext.create('Ext.form.Panel', {
99325  *         title: 'FieldContainer Example',
99326  *         width: 550,
99327  *         bodyPadding: 10,
99328  * 
99329  *         items: [{
99330  *             xtype: 'fieldcontainer',
99331  *             fieldLabel: 'Last Three Jobs',
99332  *             labelWidth: 100,
99333  * 
99334  *             // The body area will contain three text fields, arranged
99335  *             // horizontally, separated by draggable splitters.
99336  *             layout: 'hbox',
99337  *             items: [{
99338  *                 xtype: 'textfield',
99339  *                 flex: 1
99340  *             }, {
99341  *                 xtype: 'splitter'
99342  *             }, {
99343  *                 xtype: 'textfield',
99344  *                 flex: 1
99345  *             }, {
99346  *                 xtype: 'splitter'
99347  *             }, {
99348  *                 xtype: 'textfield',
99349  *                 flex: 1
99350  *             }]
99351  *         }],
99352  *         renderTo: Ext.getBody()
99353  *     });
99354  * 
99355  * # Usage of fieldDefaults
99356  *
99357  *     @example
99358  *     Ext.create('Ext.form.Panel', {
99359  *         title: 'FieldContainer Example',
99360  *         width: 350,
99361  *         bodyPadding: 10,
99362  * 
99363  *         items: [{
99364  *             xtype: 'fieldcontainer',
99365  *             fieldLabel: 'Your Name',
99366  *             labelWidth: 75,
99367  *             defaultType: 'textfield',
99368  * 
99369  *             // Arrange fields vertically, stretched to full width
99370  *             layout: 'anchor',
99371  *             defaults: {
99372  *                 layout: '100%'
99373  *             },
99374  * 
99375  *             // These config values will be applied to both sub-fields, except
99376  *             // for Last Name which will use its own msgTarget.
99377  *             fieldDefaults: {
99378  *                 msgTarget: 'under',
99379  *                 labelAlign: 'top'
99380  *             },
99381  * 
99382  *             items: [{
99383  *                 fieldLabel: 'First Name',
99384  *                 name: 'firstName'
99385  *             }, {
99386  *                 fieldLabel: 'Last Name',
99387  *                 name: 'lastName',
99388  *                 msgTarget: 'under'
99389  *             }]
99390  *         }],
99391  *         renderTo: Ext.getBody()
99392  *     });
99393  * 
99394  * @docauthor Jason Johnston <jason@sencha.com>
99395  */
99396 Ext.define('Ext.form.FieldContainer', {
99397     extend: 'Ext.container.Container',
99398     mixins: {
99399         labelable: 'Ext.form.Labelable',
99400         fieldAncestor: 'Ext.form.FieldAncestor'
99401     },
99402     alias: 'widget.fieldcontainer',
99403
99404     componentLayout: 'field',
99405
99406     /**
99407      * @cfg {Boolean} combineLabels
99408      * If set to true, and there is no defined {@link #fieldLabel}, the field container will automatically
99409      * generate its label by combining the labels of all the fields it contains. Defaults to false.
99410      */
99411     combineLabels: false,
99412
99413     /**
99414      * @cfg {String} labelConnector
99415      * The string to use when joining the labels of individual sub-fields, when {@link #combineLabels} is
99416      * set to true. Defaults to ', '.
99417      */
99418     labelConnector: ', ',
99419
99420     /**
99421      * @cfg {Boolean} combineErrors
99422      * If set to true, the field container will automatically combine and display the validation errors from
99423      * all the fields it contains as a single error on the container, according to the configured
99424      * {@link #msgTarget}. Defaults to false.
99425      */
99426     combineErrors: false,
99427
99428     maskOnDisable: false,
99429
99430     initComponent: function() {
99431         var me = this,
99432             onSubCmpAddOrRemove = me.onSubCmpAddOrRemove;
99433
99434         // Init mixins
99435         me.initLabelable();
99436         me.initFieldAncestor();
99437
99438         me.callParent();
99439     },
99440
99441     /**
99442      * @protected Called when a {@link Ext.form.Labelable} instance is added to the container's subtree.
99443      * @param {Ext.form.Labelable} labelable The instance that was added
99444      */
99445     onLabelableAdded: function(labelable) {
99446         var me = this;
99447         me.mixins.fieldAncestor.onLabelableAdded.call(this, labelable);
99448         me.updateLabel();
99449     },
99450
99451     /**
99452      * @protected Called when a {@link Ext.form.Labelable} instance is removed from the container's subtree.
99453      * @param {Ext.form.Labelable} labelable The instance that was removed
99454      */
99455     onLabelableRemoved: function(labelable) {
99456         var me = this;
99457         me.mixins.fieldAncestor.onLabelableRemoved.call(this, labelable);
99458         me.updateLabel();
99459     },
99460
99461     onRender: function() {
99462         var me = this;
99463
99464         me.onLabelableRender();
99465
99466         me.callParent(arguments);
99467     },
99468
99469     initRenderTpl: function() {
99470         var me = this;
99471         if (!me.hasOwnProperty('renderTpl')) {
99472             me.renderTpl = me.getTpl('labelableRenderTpl');
99473         }
99474         return me.callParent();
99475     },
99476
99477     initRenderData: function() {
99478         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
99479     },
99480
99481     /**
99482      * Returns the combined field label if {@link #combineLabels} is set to true and if there is no
99483      * set {@link #fieldLabel}. Otherwise returns the fieldLabel like normal. You can also override
99484      * this method to provide a custom generated label.
99485      */
99486     getFieldLabel: function() {
99487         var label = this.fieldLabel || '';
99488         if (!label && this.combineLabels) {
99489             label = Ext.Array.map(this.query('[isFieldLabelable]'), function(field) {
99490                 return field.getFieldLabel();
99491             }).join(this.labelConnector);
99492         }
99493         return label;
99494     },
99495
99496     /**
99497      * @private Updates the content of the labelEl if it is rendered
99498      */
99499     updateLabel: function() {
99500         var me = this,
99501             label = me.labelEl;
99502         if (label) {
99503             label.update(me.getFieldLabel());
99504         }
99505     },
99506
99507
99508     /**
99509      * @private Fired when the error message of any field within the container changes, and updates the
99510      * combined error message to match.
99511      */
99512     onFieldErrorChange: function(field, activeError) {
99513         if (this.combineErrors) {
99514             var me = this,
99515                 oldError = me.getActiveError(),
99516                 invalidFields = Ext.Array.filter(me.query('[isFormField]'), function(field) {
99517                     return field.hasActiveError();
99518                 }),
99519                 newErrors = me.getCombinedErrors(invalidFields);
99520
99521             if (newErrors) {
99522                 me.setActiveErrors(newErrors);
99523             } else {
99524                 me.unsetActiveError();
99525             }
99526
99527             if (oldError !== me.getActiveError()) {
99528                 me.doComponentLayout();
99529             }
99530         }
99531     },
99532
99533     /**
99534      * Takes an Array of invalid {@link Ext.form.field.Field} objects and builds a combined list of error
99535      * messages from them. Defaults to prepending each message by the field name and a colon. This
99536      * can be overridden to provide custom combined error message handling, for instance changing
99537      * the format of each message or sorting the array (it is sorted in order of appearance by default).
99538      * @param {Ext.form.field.Field[]} invalidFields An Array of the sub-fields which are currently invalid.
99539      * @return {String[]} The combined list of error messages
99540      */
99541     getCombinedErrors: function(invalidFields) {
99542         var forEach = Ext.Array.forEach,
99543             errors = [];
99544         forEach(invalidFields, function(field) {
99545             forEach(field.getActiveErrors(), function(error) {
99546                 var label = field.getFieldLabel();
99547                 errors.push((label ? label + ': ' : '') + error);
99548             });
99549         });
99550         return errors;
99551     },
99552
99553     getTargetEl: function() {
99554         return this.bodyEl || this.callParent();
99555     }
99556 });
99557
99558 /**
99559  * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
99560  * {@link Ext.form.field.Checkbox} controls into columns, and provides convenience
99561  * {@link Ext.form.field.Field} methods for {@link #getValue getting}, {@link #setValue setting},
99562  * and {@link #validate validating} the group of checkboxes as a whole.
99563  *
99564  * # Validation
99565  *
99566  * Individual checkbox fields themselves have no default validation behavior, but
99567  * sometimes you want to require a user to select at least one of a group of checkboxes. CheckboxGroup
99568  * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
99569  * least one of the checkboxes, the entire group will be highlighted as invalid and the
99570  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.
99571  *
99572  * # Layout
99573  *
99574  * The default layout for CheckboxGroup makes it easy to arrange the checkboxes into
99575  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
99576  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
99577  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
99578  * the checkbox components at any depth will still be managed by the CheckboxGroup's validation.
99579  *
99580  *     @example
99581  *     Ext.create('Ext.form.Panel', {
99582  *         title: 'Checkbox Group',
99583  *         width: 300,
99584  *         height: 125,
99585  *         bodyPadding: 10,
99586  *         renderTo: Ext.getBody(),
99587  *         items:[{
99588  *             xtype: 'checkboxgroup',
99589  *             fieldLabel: 'Two Columns',
99590  *             // Arrange radio buttons into two columns, distributed vertically
99591  *             columns: 2,
99592  *             vertical: true,
99593  *             items: [
99594  *                 { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
99595  *                 { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true },
99596  *                 { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
99597  *                 { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
99598  *                 { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
99599  *                 { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
99600  *             ]
99601  *         }]
99602  *     });
99603  */
99604 Ext.define('Ext.form.CheckboxGroup', {
99605     extend:'Ext.form.FieldContainer',
99606     mixins: {
99607         field: 'Ext.form.field.Field'
99608     },
99609     alias: 'widget.checkboxgroup',
99610     requires: ['Ext.layout.container.CheckboxGroup', 'Ext.form.field.Base'],
99611
99612     /**
99613      * @cfg {String} name
99614      * @hide
99615      */
99616
99617     /**
99618      * @cfg {Ext.form.field.Checkbox[]/Object[]} items
99619      * An Array of {@link Ext.form.field.Checkbox Checkbox}es or Checkbox config objects to arrange in the group.
99620      */
99621
99622     /**
99623      * @cfg {String/Number/Number[]} columns
99624      * Specifies the number of columns to use when displaying grouped checkbox/radio controls using automatic layout.
99625      * This config can take several types of values:
99626      *
99627      * - 'auto' - The controls will be rendered one per column on one row and the width of each column will be evenly
99628      *   distributed based on the width of the overall field container. This is the default.
99629      * - Number - If you specific a number (e.g., 3) that number of columns will be created and the contained controls
99630      *   will be automatically distributed based on the value of {@link #vertical}.
99631      * - Array - You can also specify an array of column widths, mixing integer (fixed width) and float (percentage
99632      *   width) values as needed (e.g., [100, .25, .75]). Any integer values will be rendered first, then any float
99633      *   values will be calculated as a percentage of the remaining space. Float values do not have to add up to 1
99634      *   (100%) although if you want the controls to take up the entire field container you should do so.
99635      */
99636     columns : 'auto',
99637
99638     /**
99639      * @cfg {Boolean} vertical
99640      * True to distribute contained controls across columns, completely filling each column top to bottom before
99641      * starting on the next column. The number of controls in each column will be automatically calculated to keep
99642      * columns as even as possible. The default value is false, so that controls will be added to columns one at a time,
99643      * completely filling each row left to right before starting on the next row.
99644      */
99645     vertical : false,
99646
99647     /**
99648      * @cfg {Boolean} allowBlank
99649      * False to validate that at least one item in the group is checked. If no items are selected at
99650      * validation time, {@link #blankText} will be used as the error text.
99651      */
99652     allowBlank : true,
99653
99654     /**
99655      * @cfg {String} blankText
99656      * Error text to display if the {@link #allowBlank} validation fails
99657      */
99658     blankText : "You must select at least one item in this group",
99659
99660     // private
99661     defaultType : 'checkboxfield',
99662
99663     // private
99664     groupCls : Ext.baseCSSPrefix + 'form-check-group',
99665
99666     /**
99667      * @cfg {String} fieldBodyCls
99668      * An extra CSS class to be applied to the body content element in addition to {@link #baseBodyCls}.
99669      * Defaults to 'x-form-checkboxgroup-body'.
99670      */
99671     fieldBodyCls: Ext.baseCSSPrefix + 'form-checkboxgroup-body',
99672
99673     // private
99674     layout: 'checkboxgroup',
99675
99676     initComponent: function() {
99677         var me = this;
99678         me.callParent();
99679         me.initField();
99680     },
99681
99682     /**
99683      * Initializes the field's value based on the initial config. If the {@link #value} config is specified then we use
99684      * that to set the value; otherwise we initialize the originalValue by querying the values of all sub-checkboxes
99685      * after they have been initialized.
99686      * @protected
99687      */
99688     initValue: function() {
99689         var me = this,
99690             valueCfg = me.value;
99691         me.originalValue = me.lastValue = valueCfg || me.getValue();
99692         if (valueCfg) {
99693             me.setValue(valueCfg);
99694         }
99695     },
99696
99697     /**
99698      * When a checkbox is added to the group, monitor it for changes
99699      * @param {Object} field
99700      * @protected
99701      */
99702     onFieldAdded: function(field) {
99703         var me = this;
99704         if (field.isCheckbox) {
99705             me.mon(field, 'change', me.checkChange, me);
99706         }
99707         me.callParent(arguments);
99708     },
99709
99710     onFieldRemoved: function(field) {
99711         var me = this;
99712         if (field.isCheckbox) {
99713             me.mun(field, 'change', me.checkChange, me);
99714         }
99715         me.callParent(arguments);
99716     },
99717
99718     // private override - the group value is a complex object, compare using object serialization
99719     isEqual: function(value1, value2) {
99720         var toQueryString = Ext.Object.toQueryString;
99721         return toQueryString(value1) === toQueryString(value2);
99722     },
99723
99724     /**
99725      * Runs CheckboxGroup's validations and returns an array of any errors. The only error by default is if allowBlank
99726      * is set to true and no items are checked.
99727      * @return {String[]} Array of all validation errors
99728      */
99729     getErrors: function() {
99730         var errors = [];
99731         if (!this.allowBlank && Ext.isEmpty(this.getChecked())) {
99732             errors.push(this.blankText);
99733         }
99734         return errors;
99735     },
99736
99737     /**
99738      * @private Returns all checkbox components within the container
99739      */
99740     getBoxes: function() {
99741         return this.query('[isCheckbox]');
99742     },
99743
99744     /**
99745      * @private Convenience function which calls the given function for every checkbox in the group
99746      * @param {Function} fn The function to call
99747      * @param {Object} scope (Optional) scope object
99748      */
99749     eachBox: function(fn, scope) {
99750         Ext.Array.forEach(this.getBoxes(), fn, scope || this);
99751     },
99752
99753     /**
99754      * Returns an Array of all checkboxes in the container which are currently checked
99755      * @return {Ext.form.field.Checkbox[]} Array of Ext.form.field.Checkbox components
99756      */
99757     getChecked: function() {
99758         return Ext.Array.filter(this.getBoxes(), function(cb) {
99759             return cb.getValue();
99760         });
99761     },
99762
99763     // private override
99764     isDirty: function(){
99765         return Ext.Array.some(this.getBoxes(), function(cb) {
99766             return cb.isDirty();
99767         });
99768     },
99769
99770     // private override
99771     setReadOnly: function(readOnly) {
99772         this.eachBox(function(cb) {
99773             cb.setReadOnly(readOnly);
99774         });
99775         this.readOnly = readOnly;
99776     },
99777
99778     /**
99779      * Resets the checked state of all {@link Ext.form.field.Checkbox checkboxes} in the group to their originally
99780      * loaded values and clears any validation messages.
99781      * See {@link Ext.form.Basic}.{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}
99782      */
99783     reset: function() {
99784         var me = this,
99785             hadError = me.hasActiveError(),
99786             preventMark = me.preventMark;
99787         me.preventMark = true;
99788         me.batchChanges(function() {
99789             me.eachBox(function(cb) {
99790                 cb.reset();
99791             });
99792         });
99793         me.preventMark = preventMark;
99794         me.unsetActiveError();
99795         if (hadError) {
99796             me.doComponentLayout();
99797         }
99798     },
99799
99800     // private override
99801     resetOriginalValue: function() {
99802         // Defer resetting of originalValue until after all sub-checkboxes have been reset so we get
99803         // the correct data from getValue()
99804         Ext.defer(function() {
99805             this.callParent();
99806         }, 1, this);
99807     },
99808
99809
99810     /**
99811      * Sets the value(s) of all checkboxes in the group. The expected format is an Object of name-value pairs
99812      * corresponding to the names of the checkboxes in the group. Each pair can have either a single or multiple values:
99813      *
99814      *   - A single Boolean or String value will be passed to the `setValue` method of the checkbox with that name.
99815      *     See the rules in {@link Ext.form.field.Checkbox#setValue} for accepted values.
99816      *   - An Array of String values will be matched against the {@link Ext.form.field.Checkbox#inputValue inputValue}
99817      *     of checkboxes in the group with that name; those checkboxes whose inputValue exists in the array will be
99818      *     checked and others will be unchecked.
99819      *
99820      * If a checkbox's name is not in the mapping at all, it will be unchecked.
99821      *
99822      * An example:
99823      *
99824      *     var myCheckboxGroup = new Ext.form.CheckboxGroup({
99825      *         columns: 3,
99826      *         items: [{
99827      *             name: 'cb1',
99828      *             boxLabel: 'Single 1'
99829      *         }, {
99830      *             name: 'cb2',
99831      *             boxLabel: 'Single 2'
99832      *         }, {
99833      *             name: 'cb3',
99834      *             boxLabel: 'Single 3'
99835      *         }, {
99836      *             name: 'cbGroup',
99837      *             boxLabel: 'Grouped 1'
99838      *             inputValue: 'value1'
99839      *         }, {
99840      *             name: 'cbGroup',
99841      *             boxLabel: 'Grouped 2'
99842      *             inputValue: 'value2'
99843      *         }, {
99844      *             name: 'cbGroup',
99845      *             boxLabel: 'Grouped 3'
99846      *             inputValue: 'value3'
99847      *         }]
99848      *     });
99849      *
99850      *     myCheckboxGroup.setValue({
99851      *         cb1: true,
99852      *         cb3: false,
99853      *         cbGroup: ['value1', 'value3']
99854      *     });
99855      *
99856      * The above code will cause the checkbox named 'cb1' to be checked, as well as the first and third checkboxes named
99857      * 'cbGroup'. The other three checkboxes will be unchecked.
99858      *
99859      * @param {Object} value The mapping of checkbox names to values.
99860      * @return {Ext.form.CheckboxGroup} this
99861      */
99862     setValue: function(value) {
99863         var me = this;
99864         me.batchChanges(function() {
99865             me.eachBox(function(cb) {
99866                 var name = cb.getName(),
99867                     cbValue = false;
99868                 if (value && name in value) {
99869                     if (Ext.isArray(value[name])) {
99870                         cbValue = Ext.Array.contains(value[name], cb.inputValue);
99871                     } else {
99872                         // single value, let the checkbox's own setValue handle conversion
99873                         cbValue = value[name];
99874                     }
99875                 }
99876                 cb.setValue(cbValue);
99877             });
99878         });
99879         return me;
99880     },
99881
99882
99883     /**
99884      * Returns an object containing the values of all checked checkboxes within the group. Each key-value pair in the
99885      * object corresponds to a checkbox {@link Ext.form.field.Checkbox#name name}. If there is only one checked checkbox
99886      * with a particular name, the value of that pair will be the String {@link Ext.form.field.Checkbox#inputValue
99887      * inputValue} of that checkbox. If there are multiple checked checkboxes with that name, the value of that pair
99888      * will be an Array of the selected inputValues.
99889      *
99890      * The object format returned from this method can also be passed directly to the {@link #setValue} method.
99891      *
99892      * NOTE: In Ext 3, this method returned an array of Checkbox components; this was changed to make it more consistent
99893      * with other field components and with the {@link #setValue} argument signature. If you need the old behavior in
99894      * Ext 4+, use the {@link #getChecked} method instead.
99895      */
99896     getValue: function() {
99897         var values = {};
99898         this.eachBox(function(cb) {
99899             var name = cb.getName(),
99900                 inputValue = cb.inputValue,
99901                 bucket;
99902             if (cb.getValue()) {
99903                 if (name in values) {
99904                     bucket = values[name];
99905                     if (!Ext.isArray(bucket)) {
99906                         bucket = values[name] = [bucket];
99907                     }
99908                     bucket.push(inputValue);
99909                 } else {
99910                     values[name] = inputValue;
99911                 }
99912             }
99913         });
99914         return values;
99915     },
99916
99917     /*
99918      * Don't return any data for submit; the form will get the info from the individual checkboxes themselves.
99919      */
99920     getSubmitData: function() {
99921         return null;
99922     },
99923
99924     /*
99925      * Don't return any data for the model; the form will get the info from the individual checkboxes themselves.
99926      */
99927     getModelData: function() {
99928         return null;
99929     },
99930
99931     validate: function() {
99932         var me = this,
99933             errors = me.getErrors(),
99934             isValid = Ext.isEmpty(errors),
99935             wasValid = !me.hasActiveError();
99936
99937         if (isValid) {
99938             me.unsetActiveError();
99939         } else {
99940             me.setActiveError(errors);
99941         }
99942         if (isValid !== wasValid) {
99943             me.fireEvent('validitychange', me, isValid);
99944             me.doComponentLayout();
99945         }
99946
99947         return isValid;
99948     }
99949
99950 }, function() {
99951
99952     this.borrow(Ext.form.field.Base, ['markInvalid', 'clearInvalid']);
99953
99954 });
99955
99956
99957 /**
99958  * @private
99959  * Private utility class for managing all {@link Ext.form.field.Checkbox} fields grouped by name.
99960  */
99961 Ext.define('Ext.form.CheckboxManager', {
99962     extend: 'Ext.util.MixedCollection',
99963     singleton: true,
99964
99965     getByName: function(name) {
99966         return this.filterBy(function(item) {
99967             return item.name == name;
99968         });
99969     },
99970
99971     getWithValue: function(name, value) {
99972         return this.filterBy(function(item) {
99973             return item.name == name && item.inputValue == value;
99974         });
99975     },
99976
99977     getChecked: function(name) {
99978         return this.filterBy(function(item) {
99979             return item.name == name && item.checked;
99980         });
99981     }
99982 });
99983
99984 /**
99985  * @docauthor Jason Johnston <jason@sencha.com>
99986  *
99987  * A container for grouping sets of fields, rendered as a HTML `fieldset` element. The {@link #title}
99988  * config will be rendered as the fieldset's `legend`.
99989  *
99990  * While FieldSets commonly contain simple groups of fields, they are general {@link Ext.container.Container Containers}
99991  * and may therefore contain any type of components in their {@link #items}, including other nested containers.
99992  * The default {@link #layout} for the FieldSet's items is `'anchor'`, but it can be configured to use any other
99993  * layout type.
99994  *
99995  * FieldSets may also be collapsed if configured to do so; this can be done in two ways:
99996  *
99997  * 1. Set the {@link #collapsible} config to true; this will result in a collapse button being rendered next to
99998  *    the {@link #title legend title}, or:
99999  * 2. Set the {@link #checkboxToggle} config to true; this is similar to using {@link #collapsible} but renders
100000  *    a {@link Ext.form.field.Checkbox checkbox} in place of the toggle button. The fieldset will be expanded when the
100001  *    checkbox is checked and collapsed when it is unchecked. The checkbox will also be included in the
100002  *    {@link Ext.form.Basic#submit form submit parameters} using the {@link #checkboxName} as its parameter name.
100003  *
100004  * # Example usage
100005  *
100006  *     @example
100007  *     Ext.create('Ext.form.Panel', {
100008  *         title: 'Simple Form with FieldSets',
100009  *         labelWidth: 75, // label settings here cascade unless overridden
100010  *         url: 'save-form.php',
100011  *         frame: true,
100012  *         bodyStyle: 'padding:5px 5px 0',
100013  *         width: 550,
100014  *         renderTo: Ext.getBody(),
100015  *         layout: 'column', // arrange fieldsets side by side
100016  *         defaults: {
100017  *             bodyPadding: 4
100018  *         },
100019  *         items: [{
100020  *             // Fieldset in Column 1 - collapsible via toggle button
100021  *             xtype:'fieldset',
100022  *             columnWidth: 0.5,
100023  *             title: 'Fieldset 1',
100024  *             collapsible: true,
100025  *             defaultType: 'textfield',
100026  *             defaults: {anchor: '100%'},
100027  *             layout: 'anchor',
100028  *             items :[{
100029  *                 fieldLabel: 'Field 1',
100030  *                 name: 'field1'
100031  *             }, {
100032  *                 fieldLabel: 'Field 2',
100033  *                 name: 'field2'
100034  *             }]
100035  *         }, {
100036  *             // Fieldset in Column 2 - collapsible via checkbox, collapsed by default, contains a panel
100037  *             xtype:'fieldset',
100038  *             title: 'Show Panel', // title or checkboxToggle creates fieldset header
100039  *             columnWidth: 0.5,
100040  *             checkboxToggle: true,
100041  *             collapsed: true, // fieldset initially collapsed
100042  *             layout:'anchor',
100043  *             items :[{
100044  *                 xtype: 'panel',
100045  *                 anchor: '100%',
100046  *                 title: 'Panel inside a fieldset',
100047  *                 frame: true,
100048  *                 height: 52
100049  *             }]
100050  *         }]
100051  *     });
100052  */
100053 Ext.define('Ext.form.FieldSet', {
100054     extend: 'Ext.container.Container',
100055     alias: 'widget.fieldset',
100056     uses: ['Ext.form.field.Checkbox', 'Ext.panel.Tool', 'Ext.layout.container.Anchor', 'Ext.layout.component.FieldSet'],
100057
100058     /**
100059      * @cfg {String} title
100060      * A title to be displayed in the fieldset's legend. May contain HTML markup.
100061      */
100062
100063     /**
100064      * @cfg {Boolean} [checkboxToggle=false]
100065      * Set to true to render a checkbox into the fieldset frame just in front of the legend to expand/collapse the
100066      * fieldset when the checkbox is toggled.. This checkbox will be included in form submits using
100067      * the {@link #checkboxName}.
100068      */
100069
100070     /**
100071      * @cfg {String} checkboxName
100072      * The name to assign to the fieldset's checkbox if {@link #checkboxToggle} = true
100073      * (defaults to '[fieldset id]-checkbox').
100074      */
100075
100076     /**
100077      * @cfg {Boolean} [collapsible=false]
100078      * Set to true to make the fieldset collapsible and have the expand/collapse toggle button automatically rendered
100079      * into the legend element, false to keep the fieldset statically sized with no collapse button.
100080      * Another option is to configure {@link #checkboxToggle}. Use the {@link #collapsed} config to collapse the
100081      * fieldset by default.
100082      */
100083
100084     /**
100085      * @cfg {Boolean} collapsed
100086      * Set to true to render the fieldset as collapsed by default. If {@link #checkboxToggle} is specified, the checkbox
100087      * will also be unchecked by default.
100088      */
100089     collapsed: false,
100090
100091     /**
100092      * @property {Ext.Component} legend
100093      * The component for the fieldset's legend. Will only be defined if the configuration requires a legend to be
100094      * created, by setting the {@link #title} or {@link #checkboxToggle} options.
100095      */
100096
100097     /**
100098      * @cfg {String} [baseCls='x-fieldset']
100099      * The base CSS class applied to the fieldset.
100100      */
100101     baseCls: Ext.baseCSSPrefix + 'fieldset',
100102
100103     /**
100104      * @cfg {String} layout
100105      * The {@link Ext.container.Container#layout} for the fieldset's immediate child items.
100106      */
100107     layout: 'anchor',
100108
100109     componentLayout: 'fieldset',
100110
100111     // No aria role necessary as fieldset has its own recognized semantics
100112     ariaRole: '',
100113
100114     renderTpl: ['<div id="{id}-body" class="{baseCls}-body"></div>'],
100115
100116     maskOnDisable: false,
100117
100118     getElConfig: function(){
100119         return {tag: 'fieldset', id: this.id};
100120     },
100121
100122     initComponent: function() {
100123         var me = this,
100124             baseCls = me.baseCls;
100125
100126         me.callParent();
100127
100128         // Create the Legend component if needed
100129         me.initLegend();
100130
100131         // Add body el
100132         me.addChildEls('body');
100133
100134         if (me.collapsed) {
100135             me.addCls(baseCls + '-collapsed');
100136             me.collapse();
100137         }
100138     },
100139
100140     // private
100141     onRender: function(container, position) {
100142         this.callParent(arguments);
100143         // Make sure the legend is created and rendered
100144         this.initLegend();
100145     },
100146
100147     /**
100148      * @private
100149      * Initialize and render the legend component if necessary
100150      */
100151     initLegend: function() {
100152         var me = this,
100153             legendItems,
100154             legend = me.legend;
100155
100156         // Create the legend component if needed and it hasn't been already
100157         if (!legend && (me.title || me.checkboxToggle || me.collapsible)) {
100158             legendItems = [];
100159
100160             // Checkbox
100161             if (me.checkboxToggle) {
100162                 legendItems.push(me.createCheckboxCmp());
100163             }
100164             // Toggle button
100165             else if (me.collapsible) {
100166                 legendItems.push(me.createToggleCmp());
100167             }
100168
100169             // Title
100170             legendItems.push(me.createTitleCmp());
100171
100172             legend = me.legend = Ext.create('Ext.container.Container', {
100173                 baseCls: me.baseCls + '-header',
100174                 ariaRole: '',
100175                 ownerCt: this,
100176                 getElConfig: function(){
100177                     var result = {
100178                         tag: 'legend',
100179                         cls: this.baseCls
100180                     };
100181
100182                     // Gecko3 will kick every <div> out of <legend> and mess up every thing.
100183                     // So here we change every <div> into <span>s. Therefore the following
100184                     // clearer is not needed and since div introduces a lot of subsequent
100185                     // problems, it is actually harmful.
100186                     if (!Ext.isGecko3) {
100187                         result.children = [{
100188                             cls: Ext.baseCSSPrefix + 'clear'
100189                         }];
100190                     }
100191                     return result;
100192                 },
100193                 items: legendItems
100194             });
100195         }
100196
100197         // Make sure legend is rendered if the fieldset is rendered
100198         if (legend && !legend.rendered && me.rendered) {
100199             me.legend.render(me.el, me.body); //insert before body element
100200         }
100201     },
100202
100203     /**
100204      * Creates the legend title component. This is only called internally, but could be overridden in subclasses to
100205      * customize the title component.
100206      * @return Ext.Component
100207      * @protected
100208      */
100209     createTitleCmp: function() {
100210         var me = this;
100211         me.titleCmp = Ext.create('Ext.Component', {
100212             html: me.title,
100213             getElConfig: function() {
100214                 return {
100215                     tag: Ext.isGecko3 ? 'span' : 'div',
100216                     cls: me.titleCmp.cls,
100217                     id: me.titleCmp.id
100218                 };
100219             },
100220             cls: me.baseCls + '-header-text'
100221         });
100222         return me.titleCmp;
100223     },
100224
100225     /**
100226      * @property {Ext.form.field.Checkbox} checkboxCmp
100227      * Refers to the {@link Ext.form.field.Checkbox} component that is added next to the title in the legend. Only
100228      * populated if the fieldset is configured with {@link #checkboxToggle}:true.
100229      */
100230
100231     /**
100232      * Creates the checkbox component. This is only called internally, but could be overridden in subclasses to
100233      * customize the checkbox's configuration or even return an entirely different component type.
100234      * @return Ext.Component
100235      * @protected
100236      */
100237     createCheckboxCmp: function() {
100238         var me = this,
100239             suffix = '-checkbox';
100240
100241         me.checkboxCmp = Ext.create('Ext.form.field.Checkbox', {
100242             getElConfig: function() {
100243                 return {
100244                     tag: Ext.isGecko3 ? 'span' : 'div',
100245                     id: me.checkboxCmp.id,
100246                     cls: me.checkboxCmp.cls
100247                 };
100248             },
100249             name: me.checkboxName || me.id + suffix,
100250             cls: me.baseCls + '-header' + suffix,
100251             checked: !me.collapsed,
100252             listeners: {
100253                 change: me.onCheckChange,
100254                 scope: me
100255             }
100256         });
100257         return me.checkboxCmp;
100258     },
100259
100260     /**
100261      * @property {Ext.panel.Tool} toggleCmp
100262      * Refers to the {@link Ext.panel.Tool} component that is added as the collapse/expand button next to the title in
100263      * the legend. Only populated if the fieldset is configured with {@link #collapsible}:true.
100264      */
100265
100266     /**
100267      * Creates the toggle button component. This is only called internally, but could be overridden in subclasses to
100268      * customize the toggle component.
100269      * @return Ext.Component
100270      * @protected
100271      */
100272     createToggleCmp: function() {
100273         var me = this;
100274         me.toggleCmp = Ext.create('Ext.panel.Tool', {
100275             getElConfig: function() {
100276                 return {
100277                     tag: Ext.isGecko3 ? 'span' : 'div',
100278                     id: me.toggleCmp.id,
100279                     cls: me.toggleCmp.cls
100280                 };
100281             },
100282             type: 'toggle',
100283             handler: me.toggle,
100284             scope: me
100285         });
100286         return me.toggleCmp;
100287     },
100288
100289     /**
100290      * Sets the title of this fieldset
100291      * @param {String} title The new title
100292      * @return {Ext.form.FieldSet} this
100293      */
100294     setTitle: function(title) {
100295         var me = this;
100296         me.title = title;
100297         me.initLegend();
100298         me.titleCmp.update(title);
100299         return me;
100300     },
100301
100302     getTargetEl : function() {
100303         return this.body || this.frameBody || this.el;
100304     },
100305
100306     getContentTarget: function() {
100307         return this.body;
100308     },
100309
100310     /**
100311      * @private
100312      * Include the legend component in the items for ComponentQuery
100313      */
100314     getRefItems: function(deep) {
100315         var refItems = this.callParent(arguments),
100316             legend = this.legend;
100317
100318         // Prepend legend items to ensure correct order
100319         if (legend) {
100320             refItems.unshift(legend);
100321             if (deep) {
100322                 refItems.unshift.apply(refItems, legend.getRefItems(true));
100323             }
100324         }
100325         return refItems;
100326     },
100327
100328     /**
100329      * Expands the fieldset.
100330      * @return {Ext.form.FieldSet} this
100331      */
100332     expand : function(){
100333         return this.setExpanded(true);
100334     },
100335
100336     /**
100337      * Collapses the fieldset.
100338      * @return {Ext.form.FieldSet} this
100339      */
100340     collapse : function() {
100341         return this.setExpanded(false);
100342     },
100343
100344     /**
100345      * @private Collapse or expand the fieldset
100346      */
100347     setExpanded: function(expanded) {
100348         var me = this,
100349             checkboxCmp = me.checkboxCmp;
100350
100351         expanded = !!expanded;
100352
100353         if (checkboxCmp) {
100354             checkboxCmp.setValue(expanded);
100355         }
100356
100357         if (expanded) {
100358             me.removeCls(me.baseCls + '-collapsed');
100359         } else {
100360             me.addCls(me.baseCls + '-collapsed');
100361         }
100362         me.collapsed = !expanded;
100363         if (expanded) {
100364             // ensure subitems will get rendered and layed out when expanding
100365             me.getComponentLayout().childrenChanged = true;
100366         }
100367         me.doComponentLayout();
100368         return me;
100369     },
100370
100371     /**
100372      * Toggle the fieldset's collapsed state to the opposite of what it is currently
100373      */
100374     toggle: function() {
100375         this.setExpanded(!!this.collapsed);
100376     },
100377
100378     /**
100379      * @private
100380      * Handle changes in the checkbox checked state
100381      */
100382     onCheckChange: function(cmp, checked) {
100383         this.setExpanded(checked);
100384     },
100385
100386     beforeDestroy : function() {
100387         var legend = this.legend;
100388         if (legend) {
100389             legend.destroy();
100390         }
100391         this.callParent();
100392     }
100393 });
100394
100395 /**
100396  * @docauthor Jason Johnston <jason@sencha.com>
100397  *
100398  * Produces a standalone `<label />` element which can be inserted into a form and be associated with a field
100399  * in that form using the {@link #forId} property.
100400  * 
100401  * **NOTE:** in most cases it will be more appropriate to use the {@link Ext.form.Labelable#fieldLabel fieldLabel}
100402  * and associated config properties ({@link Ext.form.Labelable#labelAlign}, {@link Ext.form.Labelable#labelWidth},
100403  * etc.) in field components themselves, as that allows labels to be uniformly sized throughout the form.
100404  * Ext.form.Label should only be used when your layout can not be achieved with the standard
100405  * {@link Ext.form.Labelable field layout}.
100406  * 
100407  * You will likely be associating the label with a field component that extends {@link Ext.form.field.Base}, so
100408  * you should make sure the {@link #forId} is set to the same value as the {@link Ext.form.field.Base#inputId inputId}
100409  * of that field.
100410  * 
100411  * The label's text can be set using either the {@link #text} or {@link #html} configuration properties; the
100412  * difference between the two is that the former will automatically escape HTML characters when rendering, while
100413  * the latter will not.
100414  *
100415  * # Example
100416  * 
100417  * This example creates a Label after its associated Text field, an arrangement that cannot currently
100418  * be achieved using the standard Field layout's labelAlign.
100419  * 
100420  *     @example
100421  *     Ext.create('Ext.form.Panel', {
100422  *         title: 'Field with Label',
100423  *         width: 400,
100424  *         bodyPadding: 10,
100425  *         renderTo: Ext.getBody(),
100426  *         layout: {
100427  *             type: 'hbox',
100428  *             align: 'middle'
100429  *         },
100430  *         items: [{
100431  *             xtype: 'textfield',
100432  *             hideLabel: true,
100433  *             flex: 1
100434  *         }, {
100435  *             xtype: 'label',
100436  *             forId: 'myFieldId',
100437  *             text: 'My Awesome Field',
100438  *             margins: '0 0 0 10'
100439  *         }]
100440  *     });
100441  */
100442 Ext.define('Ext.form.Label', {
100443     extend:'Ext.Component',
100444     alias: 'widget.label',
100445     requires: ['Ext.util.Format'],
100446
100447     /**
100448      * @cfg {String} [text='']
100449      * The plain text to display within the label. If you need to include HTML
100450      * tags within the label's innerHTML, use the {@link #html} config instead.
100451      */
100452     /**
100453      * @cfg {String} forId
100454      * The id of the input element to which this label will be bound via the standard HTML 'for'
100455      * attribute. If not specified, the attribute will not be added to the label. In most cases you will be
100456      * associating the label with a {@link Ext.form.field.Base} component, so you should make sure this matches
100457      * the {@link Ext.form.field.Base#inputId inputId} of that field.
100458      */
100459     /**
100460      * @cfg {String} [html='']
100461      * An HTML fragment that will be used as the label's innerHTML.
100462      * Note that if {@link #text} is specified it will take precedence and this value will be ignored.
100463      */
100464     
100465     maskOnDisable: false,
100466     getElConfig: function(){
100467         var me = this;
100468         return {
100469             tag: 'label', 
100470             id: me.id, 
100471             htmlFor: me.forId || '',
100472             html: me.text ? Ext.util.Format.htmlEncode(me.text) : (me.html || '') 
100473         };
100474     },
100475
100476     /**
100477      * Updates the label's innerHTML with the specified string.
100478      * @param {String} text The new label text
100479      * @param {Boolean} [encode=true] False to skip HTML-encoding the text when rendering it
100480      * to the label. This might be useful if you want to include tags in the label's innerHTML rather
100481      * than rendering them as string literals per the default logic.
100482      * @return {Ext.form.Label} this
100483      */
100484     setText : function(text, encode){
100485         var me = this;
100486         
100487         encode = encode !== false;
100488         if(encode) {
100489             me.text = text;
100490             delete me.html;
100491         } else {
100492             me.html = text;
100493             delete me.text;
100494         }
100495         
100496         if(me.rendered){
100497             me.el.dom.innerHTML = encode !== false ? Ext.util.Format.htmlEncode(text) : text;
100498         }
100499         return this;
100500     }
100501 });
100502
100503
100504 /**
100505  * @docauthor Jason Johnston <jason@sencha.com>
100506  * 
100507  * FormPanel provides a standard container for forms. It is essentially a standard {@link Ext.panel.Panel} which
100508  * automatically creates a {@link Ext.form.Basic BasicForm} for managing any {@link Ext.form.field.Field}
100509  * objects that are added as descendants of the panel. It also includes conveniences for configuring and
100510  * working with the BasicForm and the collection of Fields.
100511  * 
100512  * # Layout
100513  * 
100514  * By default, FormPanel is configured with `{@link Ext.layout.container.Anchor layout:'anchor'}` for
100515  * the layout of its immediate child items. This can be changed to any of the supported container layouts.
100516  * The layout of sub-containers is configured in {@link Ext.container.Container#layout the standard way}.
100517  * 
100518  * # BasicForm
100519  * 
100520  * Although **not listed** as configuration options of FormPanel, the FormPanel class accepts all
100521  * of the config options supported by the {@link Ext.form.Basic} class, and will pass them along to
100522  * the internal BasicForm when it is created.
100523  * 
100524  * **Note**: If subclassing FormPanel, any configuration options for the BasicForm must be applied to
100525  * the `initialConfig` property of the FormPanel. Applying {@link Ext.form.Basic BasicForm}
100526  * configuration settings to `this` will *not* affect the BasicForm's configuration.
100527  * 
100528  * The following events fired by the BasicForm will be re-fired by the FormPanel and can therefore be
100529  * listened for on the FormPanel itself:
100530  * 
100531  * - {@link Ext.form.Basic#beforeaction beforeaction}
100532  * - {@link Ext.form.Basic#actionfailed actionfailed}
100533  * - {@link Ext.form.Basic#actioncomplete actioncomplete}
100534  * - {@link Ext.form.Basic#validitychange validitychange}
100535  * - {@link Ext.form.Basic#dirtychange dirtychange}
100536  * 
100537  * # Field Defaults
100538  * 
100539  * The {@link #fieldDefaults} config option conveniently allows centralized configuration of default values
100540  * for all fields added as descendants of the FormPanel. Any config option recognized by implementations
100541  * of {@link Ext.form.Labelable} may be included in this object. See the {@link #fieldDefaults} documentation
100542  * for details of how the defaults are applied.
100543  * 
100544  * # Form Validation
100545  * 
100546  * With the default configuration, form fields are validated on-the-fly while the user edits their values.
100547  * This can be controlled on a per-field basis (or via the {@link #fieldDefaults} config) with the field
100548  * config properties {@link Ext.form.field.Field#validateOnChange} and {@link Ext.form.field.Base#checkChangeEvents},
100549  * and the FormPanel's config properties {@link #pollForChanges} and {@link #pollInterval}.
100550  * 
100551  * Any component within the FormPanel can be configured with `formBind: true`. This will cause that
100552  * component to be automatically disabled when the form is invalid, and enabled when it is valid. This is most
100553  * commonly used for Button components to prevent submitting the form in an invalid state, but can be used on
100554  * any component type.
100555  * 
100556  * For more information on form validation see the following:
100557  * 
100558  * - {@link Ext.form.field.Field#validateOnChange}
100559  * - {@link #pollForChanges} and {@link #pollInterval}
100560  * - {@link Ext.form.field.VTypes}
100561  * - {@link Ext.form.Basic#doAction BasicForm.doAction clientValidation notes}
100562  * 
100563  * # Form Submission
100564  * 
100565  * By default, Ext Forms are submitted through Ajax, using {@link Ext.form.action.Action}. See the documentation for
100566  * {@link Ext.form.Basic} for details.
100567  *
100568  * # Example usage
100569  * 
100570  *     @example
100571  *     Ext.create('Ext.form.Panel', {
100572  *         title: 'Simple Form',
100573  *         bodyPadding: 5,
100574  *         width: 350,
100575  * 
100576  *         // The form will submit an AJAX request to this URL when submitted
100577  *         url: 'save-form.php',
100578  * 
100579  *         // Fields will be arranged vertically, stretched to full width
100580  *         layout: 'anchor',
100581  *         defaults: {
100582  *             anchor: '100%'
100583  *         },
100584  * 
100585  *         // The fields
100586  *         defaultType: 'textfield',
100587  *         items: [{
100588  *             fieldLabel: 'First Name',
100589  *             name: 'first',
100590  *             allowBlank: false
100591  *         },{
100592  *             fieldLabel: 'Last Name',
100593  *             name: 'last',
100594  *             allowBlank: false
100595  *         }],
100596  * 
100597  *         // Reset and Submit buttons
100598  *         buttons: [{
100599  *             text: 'Reset',
100600  *             handler: function() {
100601  *                 this.up('form').getForm().reset();
100602  *             }
100603  *         }, {
100604  *             text: 'Submit',
100605  *             formBind: true, //only enabled once the form is valid
100606  *             disabled: true,
100607  *             handler: function() {
100608  *                 var form = this.up('form').getForm();
100609  *                 if (form.isValid()) {
100610  *                     form.submit({
100611  *                         success: function(form, action) {
100612  *                            Ext.Msg.alert('Success', action.result.msg);
100613  *                         },
100614  *                         failure: function(form, action) {
100615  *                             Ext.Msg.alert('Failed', action.result.msg);
100616  *                         }
100617  *                     });
100618  *                 }
100619  *             }
100620  *         }],
100621  *         renderTo: Ext.getBody()
100622  *     });
100623  *
100624  */
100625 Ext.define('Ext.form.Panel', {
100626     extend:'Ext.panel.Panel',
100627     mixins: {
100628         fieldAncestor: 'Ext.form.FieldAncestor'
100629     },
100630     alias: 'widget.form',
100631     alternateClassName: ['Ext.FormPanel', 'Ext.form.FormPanel'],
100632     requires: ['Ext.form.Basic', 'Ext.util.TaskRunner'],
100633
100634     /**
100635      * @cfg {Boolean} pollForChanges
100636      * If set to `true`, sets up an interval task (using the {@link #pollInterval}) in which the
100637      * panel's fields are repeatedly checked for changes in their values. This is in addition to the normal detection
100638      * each field does on its own input element, and is not needed in most cases. It does, however, provide a
100639      * means to absolutely guarantee detection of all changes including some edge cases in some browsers which
100640      * do not fire native events. Defaults to `false`.
100641      */
100642
100643     /**
100644      * @cfg {Number} pollInterval
100645      * Interval in milliseconds at which the form's fields are checked for value changes. Only used if
100646      * the {@link #pollForChanges} option is set to `true`. Defaults to 500 milliseconds.
100647      */
100648
100649     /**
100650      * @cfg {String} layout
100651      * The {@link Ext.container.Container#layout} for the form panel's immediate child items.
100652      * Defaults to `'anchor'`.
100653      */
100654     layout: 'anchor',
100655
100656     ariaRole: 'form',
100657
100658     initComponent: function() {
100659         var me = this;
100660
100661         if (me.frame) {
100662             me.border = false;
100663         }
100664
100665         me.initFieldAncestor();
100666         me.callParent();
100667
100668         me.relayEvents(me.form, [
100669             'beforeaction',
100670             'actionfailed',
100671             'actioncomplete',
100672             'validitychange',
100673             'dirtychange'
100674         ]);
100675
100676         // Start polling if configured
100677         if (me.pollForChanges) {
100678             me.startPolling(me.pollInterval || 500);
100679         }
100680     },
100681
100682     initItems: function() {
100683         // Create the BasicForm
100684         var me = this;
100685
100686         me.form = me.createForm();
100687         me.callParent();
100688         me.form.initialize();
100689     },
100690
100691     /**
100692      * @private
100693      */
100694     createForm: function() {
100695         return Ext.create('Ext.form.Basic', this, Ext.applyIf({listeners: {}}, this.initialConfig));
100696     },
100697
100698     /**
100699      * Provides access to the {@link Ext.form.Basic Form} which this Panel contains.
100700      * @return {Ext.form.Basic} The {@link Ext.form.Basic Form} which this Panel contains.
100701      */
100702     getForm: function() {
100703         return this.form;
100704     },
100705
100706     /**
100707      * Loads an {@link Ext.data.Model} into this form (internally just calls {@link Ext.form.Basic#loadRecord})
100708      * See also {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}.
100709      * @param {Ext.data.Model} record The record to load
100710      * @return {Ext.form.Basic} The Ext.form.Basic attached to this FormPanel
100711      */
100712     loadRecord: function(record) {
100713         return this.getForm().loadRecord(record);
100714     },
100715
100716     /**
100717      * Returns the currently loaded Ext.data.Model instance if one was loaded via {@link #loadRecord}.
100718      * @return {Ext.data.Model} The loaded instance
100719      */
100720     getRecord: function() {
100721         return this.getForm().getRecord();
100722     },
100723
100724     /**
100725      * Convenience function for fetching the current value of each field in the form. This is the same as calling
100726      * {@link Ext.form.Basic#getValues this.getForm().getValues()}
100727      * @return {Object} The current form field values, keyed by field name
100728      */
100729     getValues: function() {
100730         return this.getForm().getValues();
100731     },
100732
100733     beforeDestroy: function() {
100734         this.stopPolling();
100735         this.form.destroy();
100736         this.callParent();
100737     },
100738
100739     /**
100740      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#load} call.
100741      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#load} and
100742      * {@link Ext.form.Basic#doAction} for details)
100743      */
100744     load: function(options) {
100745         this.form.load(options);
100746     },
100747
100748     /**
100749      * This is a proxy for the underlying BasicForm's {@link Ext.form.Basic#submit} call.
100750      * @param {Object} options The options to pass to the action (see {@link Ext.form.Basic#submit} and
100751      * {@link Ext.form.Basic#doAction} for details)
100752      */
100753     submit: function(options) {
100754         this.form.submit(options);
100755     },
100756
100757     /*
100758      * Inherit docs, not using onDisable because it only gets fired
100759      * when the component is rendered.
100760      */
100761     disable: function(silent) {
100762         this.callParent(arguments);
100763         this.form.getFields().each(function(field) {
100764             field.disable();
100765         });
100766     },
100767
100768     /*
100769      * Inherit docs, not using onEnable because it only gets fired
100770      * when the component is rendered.
100771      */
100772     enable: function(silent) {
100773         this.callParent(arguments);
100774         this.form.getFields().each(function(field) {
100775             field.enable();
100776         });
100777     },
100778
100779     /**
100780      * Start an interval task to continuously poll all the fields in the form for changes in their
100781      * values. This is normally started automatically by setting the {@link #pollForChanges} config.
100782      * @param {Number} interval The interval in milliseconds at which the check should run.
100783      */
100784     startPolling: function(interval) {
100785         this.stopPolling();
100786         var task = Ext.create('Ext.util.TaskRunner', interval);
100787         task.start({
100788             interval: 0,
100789             run: this.checkChange,
100790             scope: this
100791         });
100792         this.pollTask = task;
100793     },
100794
100795     /**
100796      * Stop a running interval task that was started by {@link #startPolling}.
100797      */
100798     stopPolling: function() {
100799         var task = this.pollTask;
100800         if (task) {
100801             task.stopAll();
100802             delete this.pollTask;
100803         }
100804     },
100805
100806     /**
100807      * Forces each field within the form panel to
100808      * {@link Ext.form.field.Field#checkChange check if its value has changed}.
100809      */
100810     checkChange: function() {
100811         this.form.getFields().each(function(field) {
100812             field.checkChange();
100813         });
100814     }
100815 });
100816
100817 /**
100818  * A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
100819  * {@link Ext.form.field.Radio} controls into columns, and provides convenience {@link Ext.form.field.Field}
100820  * methods for {@link #getValue getting}, {@link #setValue setting}, and {@link #validate validating} the
100821  * group of radio buttons as a whole.
100822  *
100823  * # Validation
100824  *
100825  * Individual radio buttons themselves have no default validation behavior, but
100826  * sometimes you want to require a user to select one of a group of radios. RadioGroup
100827  * allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
100828  * one of the radio buttons, the entire group will be highlighted as invalid and the
100829  * {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.</p>
100830  *
100831  * # Layout
100832  *
100833  * The default layout for RadioGroup makes it easy to arrange the radio buttons into
100834  * columns; see the {@link #columns} and {@link #vertical} config documentation for details. You may also
100835  * use a completely different layout by setting the {@link #layout} to one of the other supported layout
100836  * types; for instance you may wish to use a custom arrangement of hbox and vbox containers. In that case
100837  * the Radio components at any depth will still be managed by the RadioGroup's validation.
100838  *
100839  * # Example usage
100840  *
100841  *     @example
100842  *     Ext.create('Ext.form.Panel', {
100843  *         title: 'RadioGroup Example',
100844  *         width: 300,
100845  *         height: 125,
100846  *         bodyPadding: 10,
100847  *         renderTo: Ext.getBody(),
100848  *         items:[{
100849  *             xtype: 'radiogroup',
100850  *             fieldLabel: 'Two Columns',
100851  *             // Arrange radio buttons into two columns, distributed vertically
100852  *             columns: 2,
100853  *             vertical: true,
100854  *             items: [
100855  *                 { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
100856  *                 { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true},
100857  *                 { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
100858  *                 { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
100859  *                 { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
100860  *                 { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
100861  *             ]
100862  *         }]
100863  *     });
100864  *
100865  */
100866 Ext.define('Ext.form.RadioGroup', {
100867     extend: 'Ext.form.CheckboxGroup',
100868     alias: 'widget.radiogroup',
100869
100870     /**
100871      * @cfg {Ext.form.field.Radio[]/Object[]} items
100872      * An Array of {@link Ext.form.field.Radio Radio}s or Radio config objects to arrange in the group.
100873      */
100874     /**
100875      * @cfg {Boolean} allowBlank True to allow every item in the group to be blank.
100876      * If allowBlank = false and no items are selected at validation time, {@link #blankText} will
100877      * be used as the error text.
100878      */
100879     allowBlank : true,
100880     /**
100881      * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails
100882      */
100883     blankText : 'You must select one item in this group',
100884
100885     // private
100886     defaultType : 'radiofield',
100887
100888     // private
100889     groupCls : Ext.baseCSSPrefix + 'form-radio-group',
100890
100891     getBoxes: function() {
100892         return this.query('[isRadio]');
100893     },
100894
100895     /**
100896      * Sets the value of the radio group. The radio with corresponding name and value will be set.
100897      * This method is simpler than {@link Ext.form.CheckboxGroup#setValue} because only 1 value is allowed
100898      * for each name.
100899      * 
100900      * @param {Object} value The map from names to values to be set.
100901      * @return {Ext.form.CheckboxGroup} this
100902      */
100903     setValue: function(value) {
100904         var me = this;
100905         if (Ext.isObject(value)) {
100906             Ext.Object.each(value, function(name, cbValue) {
100907                 var radios = Ext.form.RadioManager.getWithValue(name, cbValue);
100908                 radios.each(function(cb) {
100909                     cb.setValue(true);
100910                 });
100911             });
100912         }
100913         return me;
100914     }
100915 });
100916
100917 /**
100918  * @private
100919  * Private utility class for managing all {@link Ext.form.field.Radio} fields grouped by name.
100920  */
100921 Ext.define('Ext.form.RadioManager', {
100922     extend: 'Ext.util.MixedCollection',
100923     singleton: true,
100924
100925     getByName: function(name) {
100926         return this.filterBy(function(item) {
100927             return item.name == name;
100928         });
100929     },
100930
100931     getWithValue: function(name, value) {
100932         return this.filterBy(function(item) {
100933             return item.name == name && item.inputValue == value;
100934         });
100935     },
100936
100937     getChecked: function(name) {
100938         return this.findBy(function(item) {
100939             return item.name == name && item.checked;
100940         });
100941     }
100942 });
100943
100944 /**
100945  * @class Ext.form.action.DirectLoad
100946  * @extends Ext.form.action.Load
100947  * <p>Provides {@link Ext.direct.Manager} support for loading form data.</p>
100948  * <p>This example illustrates usage of Ext.direct.Direct to <b>load</b> a form through Ext.Direct.</p>
100949  * <pre><code>
100950 var myFormPanel = new Ext.form.Panel({
100951     // configs for FormPanel
100952     title: 'Basic Information',
100953     renderTo: document.body,
100954     width: 300, height: 160,
100955     padding: 10,
100956
100957     // configs apply to child items
100958     defaults: {anchor: '100%'},
100959     defaultType: 'textfield',
100960     items: [{
100961         fieldLabel: 'Name',
100962         name: 'name'
100963     },{
100964         fieldLabel: 'Email',
100965         name: 'email'
100966     },{
100967         fieldLabel: 'Company',
100968         name: 'company'
100969     }],
100970
100971     // configs for BasicForm
100972     api: {
100973         // The server-side method to call for load() requests
100974         load: Profile.getBasicInfo,
100975         // The server-side must mark the submit handler as a 'formHandler'
100976         submit: Profile.updateBasicInfo
100977     },
100978     // specify the order for the passed params
100979     paramOrder: ['uid', 'foo']
100980 });
100981
100982 // load the form
100983 myFormPanel.getForm().load({
100984     // pass 2 arguments to server side getBasicInfo method (len=2)
100985     params: {
100986         foo: 'bar',
100987         uid: 34
100988     }
100989 });
100990  * </code></pre>
100991  * The data packet sent to the server will resemble something like:
100992  * <pre><code>
100993 [
100994     {
100995         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
100996         "data":[34,"bar"] // note the order of the params
100997     }
100998 ]
100999  * </code></pre>
101000  * The form will process a data packet returned by the server that is similar
101001  * to the following format:
101002  * <pre><code>
101003 [
101004     {
101005         "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
101006         "result":{
101007             "success":true,
101008             "data":{
101009                 "name":"Fred Flintstone",
101010                 "company":"Slate Rock and Gravel",
101011                 "email":"fred.flintstone@slaterg.com"
101012             }
101013         }
101014     }
101015 ]
101016  * </code></pre>
101017  */
101018 Ext.define('Ext.form.action.DirectLoad', {
101019     extend:'Ext.form.action.Load',
101020     requires: ['Ext.direct.Manager'],
101021     alternateClassName: 'Ext.form.Action.DirectLoad',
101022     alias: 'formaction.directload',
101023
101024     type: 'directload',
101025
101026     run: function() {
101027         this.form.api.load.apply(window, this.getArgs());
101028     },
101029
101030     /**
101031      * @private
101032      * Build the arguments to be sent to the Direct call.
101033      * @return Array
101034      */
101035     getArgs: function() {
101036         var me = this,
101037             args = [],
101038             form = me.form,
101039             paramOrder = form.paramOrder,
101040             params = me.getParams(),
101041             i, len;
101042
101043         // If a paramOrder was specified, add the params into the argument list in that order.
101044         if (paramOrder) {
101045             for (i = 0, len = paramOrder.length; i < len; i++) {
101046                 args.push(params[paramOrder[i]]);
101047             }
101048         }
101049         // If paramsAsHash was specified, add all the params as a single object argument.
101050         else if (form.paramsAsHash) {
101051             args.push(params);
101052         }
101053
101054         // Add the callback and scope to the end of the arguments list
101055         args.push(me.onSuccess, me);
101056
101057         return args;
101058     },
101059
101060     // Direct actions have already been processed and therefore
101061     // we can directly set the result; Direct Actions do not have
101062     // a this.response property.
101063     processResponse: function(result) {
101064         return (this.result = result);
101065     },
101066
101067     onSuccess: function(result, trans) {
101068         if (trans.type == Ext.direct.Manager.self.exceptions.SERVER) {
101069             result = {};
101070         }
101071         this.callParent([result]);
101072     }
101073 });
101074
101075
101076
101077 /**
101078  * @class Ext.form.action.DirectSubmit
101079  * @extends Ext.form.action.Submit
101080  * <p>Provides Ext.direct support for submitting form data.</p>
101081  * <p>This example illustrates usage of Ext.direct.Direct to <b>submit</b> a form through Ext.Direct.</p>
101082  * <pre><code>
101083 var myFormPanel = new Ext.form.Panel({
101084     // configs for FormPanel
101085     title: 'Basic Information',
101086     renderTo: document.body,
101087     width: 300, height: 160,
101088     padding: 10,
101089     buttons:[{
101090         text: 'Submit',
101091         handler: function(){
101092             myFormPanel.getForm().submit({
101093                 params: {
101094                     foo: 'bar',
101095                     uid: 34
101096                 }
101097             });
101098         }
101099     }],
101100
101101     // configs apply to child items
101102     defaults: {anchor: '100%'},
101103     defaultType: 'textfield',
101104     items: [{
101105         fieldLabel: 'Name',
101106         name: 'name'
101107     },{
101108         fieldLabel: 'Email',
101109         name: 'email'
101110     },{
101111         fieldLabel: 'Company',
101112         name: 'company'
101113     }],
101114
101115     // configs for BasicForm
101116     api: {
101117         // The server-side method to call for load() requests
101118         load: Profile.getBasicInfo,
101119         // The server-side must mark the submit handler as a 'formHandler'
101120         submit: Profile.updateBasicInfo
101121     },
101122     // specify the order for the passed params
101123     paramOrder: ['uid', 'foo']
101124 });
101125  * </code></pre>
101126  * The data packet sent to the server will resemble something like:
101127  * <pre><code>
101128 {
101129     "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
101130     "result":{
101131         "success":true,
101132         "id":{
101133             "extAction":"Profile","extMethod":"updateBasicInfo",
101134             "extType":"rpc","extTID":"6","extUpload":"false",
101135             "name":"Aaron Conran","email":"aaron@sencha.com","company":"Sencha Inc."
101136         }
101137     }
101138 }
101139  * </code></pre>
101140  * The form will process a data packet returned by the server that is similar
101141  * to the following:
101142  * <pre><code>
101143 // sample success packet (batched requests)
101144 [
101145     {
101146         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":3,
101147         "result":{
101148             "success":true
101149         }
101150     }
101151 ]
101152
101153 // sample failure packet (one request)
101154 {
101155         "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
101156         "result":{
101157             "errors":{
101158                 "email":"already taken"
101159             },
101160             "success":false,
101161             "foo":"bar"
101162         }
101163 }
101164  * </code></pre>
101165  * Also see the discussion in {@link Ext.form.action.DirectLoad}.
101166  */
101167 Ext.define('Ext.form.action.DirectSubmit', {
101168     extend:'Ext.form.action.Submit',
101169     requires: ['Ext.direct.Manager'],
101170     alternateClassName: 'Ext.form.Action.DirectSubmit',
101171     alias: 'formaction.directsubmit',
101172
101173     type: 'directsubmit',
101174
101175     doSubmit: function() {
101176         var me = this,
101177             callback = Ext.Function.bind(me.onSuccess, me),
101178             formEl = me.buildForm();
101179         me.form.api.submit(formEl, callback, me);
101180         Ext.removeNode(formEl);
101181     },
101182
101183     // Direct actions have already been processed and therefore
101184     // we can directly set the result; Direct Actions do not have
101185     // a this.response property.
101186     processResponse: function(result) {
101187         return (this.result = result);
101188     },
101189
101190     onSuccess: function(response, trans) {
101191         if (trans.type === Ext.direct.Manager.self.exceptions.SERVER) {
101192             response = {};
101193         }
101194         this.callParent([response]);
101195     }
101196 });
101197
101198 /**
101199  * @class Ext.form.action.StandardSubmit
101200  * @extends Ext.form.action.Submit
101201  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s using a standard
101202  * <tt>&lt;form&gt;</tt> element submit. It does not handle the response from the submit.</p>
101203  * <p>If validation of the form fields fails, the Form's afterAction method
101204  * will be called. Otherwise, afterAction will not be called.</p>
101205  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
101206  * {@link Ext.form.Basic#submit submit}ting, when the form's {@link Ext.form.Basic#standardSubmit}
101207  * config option is <tt>true</tt>.</p>
101208  */
101209 Ext.define('Ext.form.action.StandardSubmit', {
101210     extend:'Ext.form.action.Submit',
101211     alias: 'formaction.standardsubmit',
101212
101213     /**
101214      * @cfg {String} target
101215      * Optional <tt>target</tt> attribute to be used for the form when submitting. If not specified,
101216      * the target will be the current window/frame.
101217      */
101218
101219     /**
101220      * @private
101221      * Perform the form submit. Creates and submits a temporary form element containing an input element for each
101222      * field value returned by {@link Ext.form.Basic#getValues}, plus any configured {@link #params params} or
101223      * {@link Ext.form.Basic#baseParams baseParams}.
101224      */
101225     doSubmit: function() {
101226         var form = this.buildForm();
101227         form.submit();
101228         Ext.removeNode(form);
101229     }
101230
101231 });
101232
101233 /**
101234  * @docauthor Robert Dougan <rob@sencha.com>
101235  *
101236  * Single checkbox field. Can be used as a direct replacement for traditional checkbox fields. Also serves as a
101237  * parent class for {@link Ext.form.field.Radio radio buttons}.
101238  *
101239  * # Labeling
101240  *
101241  * In addition to the {@link Ext.form.Labelable standard field labeling options}, checkboxes
101242  * may be given an optional {@link #boxLabel} which will be displayed immediately after checkbox. Also see
101243  * {@link Ext.form.CheckboxGroup} for a convenient method of grouping related checkboxes.
101244  *
101245  * # Values
101246  *
101247  * The main value of a checkbox is a boolean, indicating whether or not the checkbox is checked.
101248  * The following values will check the checkbox:
101249  *
101250  * - `true`
101251  * - `'true'`
101252  * - `'1'`
101253  * - `'on'`
101254  *
101255  * Any other value will uncheck the checkbox.
101256  *
101257  * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be
101258  * sent as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set
101259  * this value if you have multiple checkboxes with the same {@link #name}. If not specified, the value `on`
101260  * will be used.
101261  *
101262  * # Example usage
101263  *
101264  *     @example
101265  *     Ext.create('Ext.form.Panel', {
101266  *         bodyPadding: 10,
101267  *         width: 300,
101268  *         title: 'Pizza Order',
101269  *         items: [
101270  *             {
101271  *                 xtype: 'fieldcontainer',
101272  *                 fieldLabel: 'Toppings',
101273  *                 defaultType: 'checkboxfield',
101274  *                 items: [
101275  *                     {
101276  *                         boxLabel  : 'Anchovies',
101277  *                         name      : 'topping',
101278  *                         inputValue: '1',
101279  *                         id        : 'checkbox1'
101280  *                     }, {
101281  *                         boxLabel  : 'Artichoke Hearts',
101282  *                         name      : 'topping',
101283  *                         inputValue: '2',
101284  *                         checked   : true,
101285  *                         id        : 'checkbox2'
101286  *                     }, {
101287  *                         boxLabel  : 'Bacon',
101288  *                         name      : 'topping',
101289  *                         inputValue: '3',
101290  *                         id        : 'checkbox3'
101291  *                     }
101292  *                 ]
101293  *             }
101294  *         ],
101295  *         bbar: [
101296  *             {
101297  *                 text: 'Select Bacon',
101298  *                 handler: function() {
101299  *                     Ext.getCmp('checkbox3').setValue(true);
101300  *                 }
101301  *             },
101302  *             '-',
101303  *             {
101304  *                 text: 'Select All',
101305  *                 handler: function() {
101306  *                     Ext.getCmp('checkbox1').setValue(true);
101307  *                     Ext.getCmp('checkbox2').setValue(true);
101308  *                     Ext.getCmp('checkbox3').setValue(true);
101309  *                 }
101310  *             },
101311  *             {
101312  *                 text: 'Deselect All',
101313  *                 handler: function() {
101314  *                     Ext.getCmp('checkbox1').setValue(false);
101315  *                     Ext.getCmp('checkbox2').setValue(false);
101316  *                     Ext.getCmp('checkbox3').setValue(false);
101317  *                 }
101318  *             }
101319  *         ],
101320  *         renderTo: Ext.getBody()
101321  *     });
101322  */
101323 Ext.define('Ext.form.field.Checkbox', {
101324     extend: 'Ext.form.field.Base',
101325     alias: ['widget.checkboxfield', 'widget.checkbox'],
101326     alternateClassName: 'Ext.form.Checkbox',
101327     requires: ['Ext.XTemplate', 'Ext.form.CheckboxManager'],
101328
101329     // note: {id} here is really {inputId}, but {cmpId} is available
101330     fieldSubTpl: [
101331         '<tpl if="boxLabel && boxLabelAlign == \'before\'">',
101332             '<label id="{cmpId}-boxLabelEl" class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
101333         '</tpl>',
101334         // Creates not an actual checkbox, but a button which is given aria role="checkbox" and
101335         // styled with a custom checkbox image. This allows greater control and consistency in
101336         // styling, and using a button allows it to gain focus and handle keyboard nav properly.
101337         '<input type="button" id="{id}" ',
101338             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
101339             'class="{fieldCls} {typeCls}" autocomplete="off" hidefocus="true" />',
101340         '<tpl if="boxLabel && boxLabelAlign == \'after\'">',
101341             '<label id="{cmpId}-boxLabelEl" class="{boxLabelCls} {boxLabelCls}-{boxLabelAlign}" for="{id}">{boxLabel}</label>',
101342         '</tpl>',
101343         {
101344             disableFormats: true,
101345             compiled: true
101346         }
101347     ],
101348
101349     isCheckbox: true,
101350
101351     /**
101352      * @cfg {String} [focusCls='x-form-cb-focus']
101353      * The CSS class to use when the checkbox receives focus
101354      */
101355     focusCls: Ext.baseCSSPrefix + 'form-cb-focus',
101356
101357     /**
101358      * @cfg {String} [fieldCls='x-form-field']
101359      * The default CSS class for the checkbox
101360      */
101361
101362     /**
101363      * @cfg {String} [fieldBodyCls='x-form-cb-wrap']
101364      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
101365      * .
101366      */
101367     fieldBodyCls: Ext.baseCSSPrefix + 'form-cb-wrap',
101368
101369     /**
101370      * @cfg {Boolean} checked
101371      * true if the checkbox should render initially checked
101372      */
101373     checked: false,
101374
101375     /**
101376      * @cfg {String} [checkedCls='x-form-cb-checked']
101377      * The CSS class added to the component's main element when it is in the checked state.
101378      */
101379     checkedCls: Ext.baseCSSPrefix + 'form-cb-checked',
101380
101381     /**
101382      * @cfg {String} boxLabel
101383      * An optional text label that will appear next to the checkbox. Whether it appears before or after the checkbox is
101384      * determined by the {@link #boxLabelAlign} config.
101385      */
101386
101387     /**
101388      * @cfg {String} [boxLabelCls='x-form-cb-label']
101389      * The CSS class to be applied to the {@link #boxLabel} element
101390      */
101391     boxLabelCls: Ext.baseCSSPrefix + 'form-cb-label',
101392
101393     /**
101394      * @cfg {String} boxLabelAlign
101395      * The position relative to the checkbox where the {@link #boxLabel} should appear. Recognized values are 'before'
101396      * and 'after'.
101397      */
101398     boxLabelAlign: 'after',
101399
101400     /**
101401      * @cfg {String} inputValue
101402      * The value that should go into the generated input element's value attribute and should be used as the parameter
101403      * value when submitting as part of a form.
101404      */
101405     inputValue: 'on',
101406
101407     /**
101408      * @cfg {String} uncheckedValue
101409      * If configured, this will be submitted as the checkbox's value during form submit if the checkbox is unchecked. By
101410      * default this is undefined, which results in nothing being submitted for the checkbox field when the form is
101411      * submitted (the default behavior of HTML checkboxes).
101412      */
101413
101414     /**
101415      * @cfg {Function} handler
101416      * A function called when the {@link #checked} value changes (can be used instead of handling the {@link #change
101417      * change event}).
101418      * @cfg {Ext.form.field.Checkbox} handler.checkbox The Checkbox being toggled.
101419      * @cfg {Boolean} handler.checked The new checked state of the checkbox.
101420      */
101421
101422     /**
101423      * @cfg {Object} scope
101424      * An object to use as the scope ('this' reference) of the {@link #handler} function (defaults to this Checkbox).
101425      */
101426
101427     // private overrides
101428     checkChangeEvents: [],
101429     inputType: 'checkbox',
101430     ariaRole: 'checkbox',
101431
101432     // private
101433     onRe: /^on$/i,
101434
101435     initComponent: function(){
101436         this.callParent(arguments);
101437         this.getManager().add(this);
101438     },
101439
101440     initValue: function() {
101441         var me = this,
101442             checked = !!me.checked;
101443
101444         /**
101445          * @property {Object} originalValue
101446          * The original value of the field as configured in the {@link #checked} configuration, or as loaded by the last
101447          * form load operation if the form's {@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad} setting is `true`.
101448          */
101449         me.originalValue = me.lastValue = checked;
101450
101451         // Set the initial checked state
101452         me.setValue(checked);
101453     },
101454
101455     // private
101456     onRender : function(ct, position) {
101457         var me = this;
101458
101459         /**
101460          * @property {Ext.Element} boxLabelEl
101461          * A reference to the label element created for the {@link #boxLabel}. Only present if the component has been
101462          * rendered and has a boxLabel configured.
101463          */
101464         me.addChildEls('boxLabelEl');
101465
101466         Ext.applyIf(me.subTplData, {
101467             boxLabel: me.boxLabel,
101468             boxLabelCls: me.boxLabelCls,
101469             boxLabelAlign: me.boxLabelAlign
101470         });
101471
101472         me.callParent(arguments);
101473     },
101474
101475     initEvents: function() {
101476         var me = this;
101477         me.callParent();
101478         me.mon(me.inputEl, 'click', me.onBoxClick, me);
101479     },
101480
101481     /**
101482      * @private Handle click on the checkbox button
101483      */
101484     onBoxClick: function(e) {
101485         var me = this;
101486         if (!me.disabled && !me.readOnly) {
101487             this.setValue(!this.checked);
101488         }
101489     },
101490
101491     /**
101492      * Returns the checked state of the checkbox.
101493      * @return {Boolean} True if checked, else false
101494      */
101495     getRawValue: function() {
101496         return this.checked;
101497     },
101498
101499     /**
101500      * Returns the checked state of the checkbox.
101501      * @return {Boolean} True if checked, else false
101502      */
101503     getValue: function() {
101504         return this.checked;
101505     },
101506
101507     /**
101508      * Returns the submit value for the checkbox which can be used when submitting forms.
101509      * @return {Boolean/Object} True if checked; otherwise either the {@link #uncheckedValue} or null.
101510      */
101511     getSubmitValue: function() {
101512         var unchecked = this.uncheckedValue,
101513             uncheckedVal = Ext.isDefined(unchecked) ? unchecked : null;
101514         return this.checked ? this.inputValue : uncheckedVal;
101515     },
101516
101517     /**
101518      * Sets the checked state of the checkbox.
101519      *
101520      * @param {Boolean/String/Number} value The following values will check the checkbox:
101521      * `true, 'true', '1', 1, or 'on'`, as well as a String that matches the {@link #inputValue}.
101522      * Any other value will uncheck the checkbox.
101523      * @return {Boolean} the new checked state of the checkbox
101524      */
101525     setRawValue: function(value) {
101526         var me = this,
101527             inputEl = me.inputEl,
101528             inputValue = me.inputValue,
101529             checked = (value === true || value === 'true' || value === '1' || value === 1 ||
101530                 (((Ext.isString(value) || Ext.isNumber(value)) && inputValue) ? value == inputValue : me.onRe.test(value)));
101531
101532         if (inputEl) {
101533             inputEl.dom.setAttribute('aria-checked', checked);
101534             me[checked ? 'addCls' : 'removeCls'](me.checkedCls);
101535         }
101536
101537         me.checked = me.rawValue = checked;
101538         return checked;
101539     },
101540
101541     /**
101542      * Sets the checked state of the checkbox, and invokes change detection.
101543      * @param {Boolean/String} checked The following values will check the checkbox: `true, 'true', '1', or 'on'`, as
101544      * well as a String that matches the {@link #inputValue}. Any other value will uncheck the checkbox.
101545      * @return {Ext.form.field.Checkbox} this
101546      */
101547     setValue: function(checked) {
101548         var me = this;
101549
101550         // If an array of strings is passed, find all checkboxes in the group with the same name as this
101551         // one and check all those whose inputValue is in the array, unchecking all the others. This is to
101552         // facilitate setting values from Ext.form.Basic#setValues, but is not publicly documented as we
101553         // don't want users depending on this behavior.
101554         if (Ext.isArray(checked)) {
101555             me.getManager().getByName(me.name).each(function(cb) {
101556                 cb.setValue(Ext.Array.contains(checked, cb.inputValue));
101557             });
101558         } else {
101559             me.callParent(arguments);
101560         }
101561
101562         return me;
101563     },
101564
101565     // private
101566     valueToRaw: function(value) {
101567         // No extra conversion for checkboxes
101568         return value;
101569     },
101570
101571     /**
101572      * @private
101573      * Called when the checkbox's checked state changes. Invokes the {@link #handler} callback
101574      * function if specified.
101575      */
101576     onChange: function(newVal, oldVal) {
101577         var me = this,
101578             handler = me.handler;
101579         if (handler) {
101580             handler.call(me.scope || me, me, newVal);
101581         }
101582         me.callParent(arguments);
101583     },
101584
101585     // inherit docs
101586     beforeDestroy: function(){
101587         this.callParent();
101588         this.getManager().removeAtKey(this.id);
101589     },
101590
101591     // inherit docs
101592     getManager: function() {
101593         return Ext.form.CheckboxManager;
101594     },
101595
101596     onEnable: function() {
101597         var me = this,
101598             inputEl = me.inputEl;
101599         me.callParent();
101600         if (inputEl) {
101601             // Can still be disabled if the field is readOnly
101602             inputEl.dom.disabled = me.readOnly;
101603         }
101604     },
101605
101606     setReadOnly: function(readOnly) {
101607         var me = this,
101608             inputEl = me.inputEl;
101609         if (inputEl) {
101610             // Set the button to disabled when readonly
101611             inputEl.dom.disabled = readOnly || me.disabled;
101612         }
101613         me.readOnly = readOnly;
101614     },
101615
101616     // Calculates and returns the natural width of the bodyEl. It's possible that the initial rendering will
101617     // cause the boxLabel to wrap and give us a bad width, so we must prevent wrapping while measuring.
101618     getBodyNaturalWidth: function() {
101619         var me = this,
101620             bodyEl = me.bodyEl,
101621             ws = 'white-space',
101622             width;
101623         bodyEl.setStyle(ws, 'nowrap');
101624         width = bodyEl.getWidth();
101625         bodyEl.setStyle(ws, '');
101626         return width;
101627     }
101628
101629 });
101630
101631 /**
101632  * @private
101633  * @class Ext.layout.component.field.Trigger
101634  * @extends Ext.layout.component.field.Field
101635  * Layout class for {@link Ext.form.field.Trigger} fields. Adjusts the input field size to accommodate
101636  * the trigger button(s).
101637  * @private
101638  */
101639
101640 Ext.define('Ext.layout.component.field.Trigger', {
101641
101642     /* Begin Definitions */
101643
101644     alias: ['layout.triggerfield'],
101645
101646     extend: 'Ext.layout.component.field.Field',
101647
101648     /* End Definitions */
101649
101650     type: 'triggerfield',
101651
101652     sizeBodyContents: function(width, height) {
101653         var me = this,
101654             owner = me.owner,
101655             inputEl = owner.inputEl,
101656             triggerWrap = owner.triggerWrap,
101657             triggerWidth = owner.getTriggerWidth();
101658
101659         // If we or our ancestor is hidden, we can get a triggerWidth calculation
101660         // of 0.  We don't want to resize in this case.
101661         if (owner.hideTrigger || owner.readOnly || triggerWidth > 0) {
101662             // Decrease the field's width by the width of the triggers. Both the field and the triggerWrap
101663             // are floated left in CSS so they'll stack up side by side.
101664             me.setElementSize(inputEl, Ext.isNumber(width) ? width - triggerWidth : width);
101665     
101666             // Explicitly set the triggerWrap's width, to prevent wrapping
101667             triggerWrap.setWidth(triggerWidth);
101668         }
101669     }
101670 });
101671 /**
101672  * A mechanism for displaying data using custom layout templates and formatting.
101673  *
101674  * The View uses an {@link Ext.XTemplate} as its internal templating mechanism, and is bound to an
101675  * {@link Ext.data.Store} so that as the data in the store changes the view is automatically updated
101676  * to reflect the changes. The view also provides built-in behavior for many common events that can
101677  * occur for its contained items including click, doubleclick, mouseover, mouseout, etc. as well as a
101678  * built-in selection model. **In order to use these features, an {@link #itemSelector} config must
101679  * be provided for the DataView to determine what nodes it will be working with.**
101680  *
101681  * The example below binds a View to a {@link Ext.data.Store} and renders it into an {@link Ext.panel.Panel}.
101682  *
101683  *     @example
101684  *     Ext.define('Image', {
101685  *         extend: 'Ext.data.Model',
101686  *         fields: [
101687  *             { name:'src', type:'string' },
101688  *             { name:'caption', type:'string' }
101689  *         ]
101690  *     });
101691  *
101692  *     Ext.create('Ext.data.Store', {
101693  *         id:'imagesStore',
101694  *         model: 'Image',
101695  *         data: [
101696  *             { src:'http://www.sencha.com/img/20110215-feat-drawing.png', caption:'Drawing & Charts' },
101697  *             { src:'http://www.sencha.com/img/20110215-feat-data.png', caption:'Advanced Data' },
101698  *             { src:'http://www.sencha.com/img/20110215-feat-html5.png', caption:'Overhauled Theme' },
101699  *             { src:'http://www.sencha.com/img/20110215-feat-perf.png', caption:'Performance Tuned' }
101700  *         ]
101701  *     });
101702  *
101703  *     var imageTpl = new Ext.XTemplate(
101704  *         '<tpl for=".">',
101705  *             '<div style="margin-bottom: 10px;" class="thumb-wrap">',
101706  *               '<img src="{src}" />',
101707  *               '<br/><span>{caption}</span>',
101708  *             '</div>',
101709  *         '</tpl>'
101710  *     );
101711  *
101712  *     Ext.create('Ext.view.View', {
101713  *         store: Ext.data.StoreManager.lookup('imagesStore'),
101714  *         tpl: imageTpl,
101715  *         itemSelector: 'div.thumb-wrap',
101716  *         emptyText: 'No images available',
101717  *         renderTo: Ext.getBody()
101718  *     });
101719  */
101720 Ext.define('Ext.view.View', {
101721     extend: 'Ext.view.AbstractView',
101722     alternateClassName: 'Ext.DataView',
101723     alias: 'widget.dataview',
101724
101725     inheritableStatics: {
101726         EventMap: {
101727             mousedown: 'MouseDown',
101728             mouseup: 'MouseUp',
101729             click: 'Click',
101730             dblclick: 'DblClick',
101731             contextmenu: 'ContextMenu',
101732             mouseover: 'MouseOver',
101733             mouseout: 'MouseOut',
101734             mouseenter: 'MouseEnter',
101735             mouseleave: 'MouseLeave',
101736             keydown: 'KeyDown',
101737             focus: 'Focus'
101738         }
101739     },
101740
101741     addCmpEvents: function() {
101742         this.addEvents(
101743             /**
101744              * @event beforeitemmousedown
101745              * Fires before the mousedown event on an item is processed. Returns false to cancel the default action.
101746              * @param {Ext.view.View} this
101747              * @param {Ext.data.Model} record The record that belongs to the item
101748              * @param {HTMLElement} item The item's element
101749              * @param {Number} index The item's index
101750              * @param {Ext.EventObject} e The raw event object
101751              */
101752             'beforeitemmousedown',
101753             /**
101754              * @event beforeitemmouseup
101755              * Fires before the mouseup event on an item is processed. Returns false to cancel the default action.
101756              * @param {Ext.view.View} this
101757              * @param {Ext.data.Model} record The record that belongs to the item
101758              * @param {HTMLElement} item The item's element
101759              * @param {Number} index The item's index
101760              * @param {Ext.EventObject} e The raw event object
101761              */
101762             'beforeitemmouseup',
101763             /**
101764              * @event beforeitemmouseenter
101765              * Fires before the mouseenter event on an item is processed. Returns false to cancel the default action.
101766              * @param {Ext.view.View} this
101767              * @param {Ext.data.Model} record The record that belongs to the item
101768              * @param {HTMLElement} item The item's element
101769              * @param {Number} index The item's index
101770              * @param {Ext.EventObject} e The raw event object
101771              */
101772             'beforeitemmouseenter',
101773             /**
101774              * @event beforeitemmouseleave
101775              * Fires before the mouseleave event on an item is processed. Returns false to cancel the default action.
101776              * @param {Ext.view.View} this
101777              * @param {Ext.data.Model} record The record that belongs to the item
101778              * @param {HTMLElement} item The item's element
101779              * @param {Number} index The item's index
101780              * @param {Ext.EventObject} e The raw event object
101781              */
101782             'beforeitemmouseleave',
101783             /**
101784              * @event beforeitemclick
101785              * Fires before the click event on an item is processed. Returns false to cancel the default action.
101786              * @param {Ext.view.View} this
101787              * @param {Ext.data.Model} record The record that belongs to the item
101788              * @param {HTMLElement} item The item's element
101789              * @param {Number} index The item's index
101790              * @param {Ext.EventObject} e The raw event object
101791              */
101792             'beforeitemclick',
101793             /**
101794              * @event beforeitemdblclick
101795              * Fires before the dblclick event on an item is processed. Returns false to cancel the default action.
101796              * @param {Ext.view.View} this
101797              * @param {Ext.data.Model} record The record that belongs to the item
101798              * @param {HTMLElement} item The item's element
101799              * @param {Number} index The item's index
101800              * @param {Ext.EventObject} e The raw event object
101801              */
101802             'beforeitemdblclick',
101803             /**
101804              * @event beforeitemcontextmenu
101805              * Fires before the contextmenu event on an item is processed. Returns false to cancel the default action.
101806              * @param {Ext.view.View} this
101807              * @param {Ext.data.Model} record The record that belongs to the item
101808              * @param {HTMLElement} item The item's element
101809              * @param {Number} index The item's index
101810              * @param {Ext.EventObject} e The raw event object
101811              */
101812             'beforeitemcontextmenu',
101813             /**
101814              * @event beforeitemkeydown
101815              * Fires before the keydown event on an item is processed. Returns false to cancel the default action.
101816              * @param {Ext.view.View} this
101817              * @param {Ext.data.Model} record The record that belongs to the item
101818              * @param {HTMLElement} item The item's element
101819              * @param {Number} index The item's index
101820              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
101821              */
101822             'beforeitemkeydown',
101823             /**
101824              * @event itemmousedown
101825              * Fires when there is a mouse down on an item
101826              * @param {Ext.view.View} this
101827              * @param {Ext.data.Model} record The record that belongs to the item
101828              * @param {HTMLElement} item The item's element
101829              * @param {Number} index The item's index
101830              * @param {Ext.EventObject} e The raw event object
101831              */
101832             'itemmousedown',
101833             /**
101834              * @event itemmouseup
101835              * Fires when there is a mouse up on an item
101836              * @param {Ext.view.View} this
101837              * @param {Ext.data.Model} record The record that belongs to the item
101838              * @param {HTMLElement} item The item's element
101839              * @param {Number} index The item's index
101840              * @param {Ext.EventObject} e The raw event object
101841              */
101842             'itemmouseup',
101843             /**
101844              * @event itemmouseenter
101845              * Fires when the mouse enters an item.
101846              * @param {Ext.view.View} this
101847              * @param {Ext.data.Model} record The record that belongs to the item
101848              * @param {HTMLElement} item The item's element
101849              * @param {Number} index The item's index
101850              * @param {Ext.EventObject} e The raw event object
101851              */
101852             'itemmouseenter',
101853             /**
101854              * @event itemmouseleave
101855              * Fires when the mouse leaves an item.
101856              * @param {Ext.view.View} this
101857              * @param {Ext.data.Model} record The record that belongs to the item
101858              * @param {HTMLElement} item The item's element
101859              * @param {Number} index The item's index
101860              * @param {Ext.EventObject} e The raw event object
101861              */
101862             'itemmouseleave',
101863             /**
101864              * @event itemclick
101865              * Fires when an item is clicked.
101866              * @param {Ext.view.View} this
101867              * @param {Ext.data.Model} record The record that belongs to the item
101868              * @param {HTMLElement} item The item's element
101869              * @param {Number} index The item's index
101870              * @param {Ext.EventObject} e The raw event object
101871              */
101872             'itemclick',
101873             /**
101874              * @event itemdblclick
101875              * Fires when an item is double clicked.
101876              * @param {Ext.view.View} this
101877              * @param {Ext.data.Model} record The record that belongs to the item
101878              * @param {HTMLElement} item The item's element
101879              * @param {Number} index The item's index
101880              * @param {Ext.EventObject} e The raw event object
101881              */
101882             'itemdblclick',
101883             /**
101884              * @event itemcontextmenu
101885              * Fires when an item is right clicked.
101886              * @param {Ext.view.View} this
101887              * @param {Ext.data.Model} record The record that belongs to the item
101888              * @param {HTMLElement} item The item's element
101889              * @param {Number} index The item's index
101890              * @param {Ext.EventObject} e The raw event object
101891              */
101892             'itemcontextmenu',
101893             /**
101894              * @event itemkeydown
101895              * Fires when a key is pressed while an item is currently selected.
101896              * @param {Ext.view.View} this
101897              * @param {Ext.data.Model} record The record that belongs to the item
101898              * @param {HTMLElement} item The item's element
101899              * @param {Number} index The item's index
101900              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
101901              */
101902             'itemkeydown',
101903             /**
101904              * @event beforecontainermousedown
101905              * Fires before the mousedown event on the container is processed. Returns false to cancel the default action.
101906              * @param {Ext.view.View} this
101907              * @param {Ext.EventObject} e The raw event object
101908              */
101909             'beforecontainermousedown',
101910             /**
101911              * @event beforecontainermouseup
101912              * Fires before the mouseup event on the container is processed. Returns false to cancel the default action.
101913              * @param {Ext.view.View} this
101914              * @param {Ext.EventObject} e The raw event object
101915              */
101916             'beforecontainermouseup',
101917             /**
101918              * @event beforecontainermouseover
101919              * Fires before the mouseover event on the container is processed. Returns false to cancel the default action.
101920              * @param {Ext.view.View} this
101921              * @param {Ext.EventObject} e The raw event object
101922              */
101923             'beforecontainermouseover',
101924             /**
101925              * @event beforecontainermouseout
101926              * Fires before the mouseout event on the container is processed. Returns false to cancel the default action.
101927              * @param {Ext.view.View} this
101928              * @param {Ext.EventObject} e The raw event object
101929              */
101930             'beforecontainermouseout',
101931             /**
101932              * @event beforecontainerclick
101933              * Fires before the click event on the container is processed. Returns false to cancel the default action.
101934              * @param {Ext.view.View} this
101935              * @param {Ext.EventObject} e The raw event object
101936              */
101937             'beforecontainerclick',
101938             /**
101939              * @event beforecontainerdblclick
101940              * Fires before the dblclick event on the container is processed. Returns false to cancel the default action.
101941              * @param {Ext.view.View} this
101942              * @param {Ext.EventObject} e The raw event object
101943              */
101944             'beforecontainerdblclick',
101945             /**
101946              * @event beforecontainercontextmenu
101947              * Fires before the contextmenu event on the container is processed. Returns false to cancel the default action.
101948              * @param {Ext.view.View} this
101949              * @param {Ext.EventObject} e The raw event object
101950              */
101951             'beforecontainercontextmenu',
101952             /**
101953              * @event beforecontainerkeydown
101954              * Fires before the keydown event on the container is processed. Returns false to cancel the default action.
101955              * @param {Ext.view.View} this
101956              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
101957              */
101958             'beforecontainerkeydown',
101959             /**
101960              * @event containermouseup
101961              * Fires when there is a mouse up on the container
101962              * @param {Ext.view.View} this
101963              * @param {Ext.EventObject} e The raw event object
101964              */
101965             'containermouseup',
101966             /**
101967              * @event containermouseover
101968              * Fires when you move the mouse over the container.
101969              * @param {Ext.view.View} this
101970              * @param {Ext.EventObject} e The raw event object
101971              */
101972             'containermouseover',
101973             /**
101974              * @event containermouseout
101975              * Fires when you move the mouse out of the container.
101976              * @param {Ext.view.View} this
101977              * @param {Ext.EventObject} e The raw event object
101978              */
101979             'containermouseout',
101980             /**
101981              * @event containerclick
101982              * Fires when the container is clicked.
101983              * @param {Ext.view.View} this
101984              * @param {Ext.EventObject} e The raw event object
101985              */
101986             'containerclick',
101987             /**
101988              * @event containerdblclick
101989              * Fires when the container is double clicked.
101990              * @param {Ext.view.View} this
101991              * @param {Ext.EventObject} e The raw event object
101992              */
101993             'containerdblclick',
101994             /**
101995              * @event containercontextmenu
101996              * Fires when the container is right clicked.
101997              * @param {Ext.view.View} this
101998              * @param {Ext.EventObject} e The raw event object
101999              */
102000             'containercontextmenu',
102001             /**
102002              * @event containerkeydown
102003              * Fires when a key is pressed while the container is focused, and no item is currently selected.
102004              * @param {Ext.view.View} this
102005              * @param {Ext.EventObject} e The raw event object. Use {@link Ext.EventObject#getKey getKey()} to retrieve the key that was pressed.
102006              */
102007             'containerkeydown',
102008
102009             /**
102010              * @event selectionchange
102011              * Fires when the selected nodes change. Relayed event from the underlying selection model.
102012              * @param {Ext.view.View} this
102013              * @param {HTMLElement[]} selections Array of the selected nodes
102014              */
102015             'selectionchange',
102016             /**
102017              * @event beforeselect
102018              * Fires before a selection is made. If any handlers return false, the selection is cancelled.
102019              * @param {Ext.view.View} this
102020              * @param {HTMLElement} node The node to be selected
102021              * @param {HTMLElement[]} selections Array of currently selected nodes
102022              */
102023             'beforeselect'
102024         );
102025     },
102026     // private
102027     afterRender: function(){
102028         var me = this,
102029             listeners;
102030
102031         me.callParent();
102032
102033         listeners = {
102034             scope: me,
102035             /*
102036              * We need to make copies of this since some of the events fired here will end up triggering
102037              * a new event to be called and the shared event object will be mutated. In future we should
102038              * investigate if there are any issues with creating a new event object for each event that
102039              * is fired.
102040              */
102041             freezeEvent: true,
102042             click: me.handleEvent,
102043             mousedown: me.handleEvent,
102044             mouseup: me.handleEvent,
102045             dblclick: me.handleEvent,
102046             contextmenu: me.handleEvent,
102047             mouseover: me.handleEvent,
102048             mouseout: me.handleEvent,
102049             keydown: me.handleEvent
102050         };
102051
102052         me.mon(me.getTargetEl(), listeners);
102053
102054         if (me.store) {
102055             me.bindStore(me.store, true);
102056         }
102057     },
102058
102059     handleEvent: function(e) {
102060         if (this.processUIEvent(e) !== false) {
102061             this.processSpecialEvent(e);
102062         }
102063     },
102064
102065     // Private template method
102066     processItemEvent: Ext.emptyFn,
102067     processContainerEvent: Ext.emptyFn,
102068     processSpecialEvent: Ext.emptyFn,
102069
102070     /*
102071      * Returns true if this mouseover/out event is still over the overItem.
102072      */
102073     stillOverItem: function (event, overItem) {
102074         var nowOver;
102075
102076         // There is this weird bug when you hover over the border of a cell it is saying
102077         // the target is the table.
102078         // BrowserBug: IE6 & 7. If me.mouseOverItem has been removed and is no longer
102079         // in the DOM then accessing .offsetParent will throw an "Unspecified error." exception.
102080         // typeof'ng and checking to make sure the offsetParent is an object will NOT throw
102081         // this hard exception.
102082         if (overItem && typeof(overItem.offsetParent) === "object") {
102083             // mouseout : relatedTarget == nowOver, target == wasOver
102084             // mouseover: relatedTarget == wasOver, target == nowOver
102085             nowOver = (event.type == 'mouseout') ? event.getRelatedTarget() : event.getTarget();
102086             return Ext.fly(overItem).contains(nowOver);
102087         }
102088
102089         return false;
102090     },
102091
102092     processUIEvent: function(e) {
102093         var me = this,
102094             item = e.getTarget(me.getItemSelector(), me.getTargetEl()),
102095             map = this.statics().EventMap,
102096             index, record,
102097             type = e.type,
102098             overItem = me.mouseOverItem,
102099             newType;
102100
102101         if (!item) {
102102             if (type == 'mouseover' && me.stillOverItem(e, overItem)) {
102103                 item = overItem;
102104             }
102105
102106             // Try to get the selected item to handle the keydown event, otherwise we'll just fire a container keydown event
102107             if (type == 'keydown') {
102108                 record = me.getSelectionModel().getLastSelected();
102109                 if (record) {
102110                     item = me.getNode(record);
102111                 }
102112             }
102113         }
102114
102115         if (item) {
102116             index = me.indexOf(item);
102117             if (!record) {
102118                 record = me.getRecord(item);
102119             }
102120
102121             if (me.processItemEvent(record, item, index, e) === false) {
102122                 return false;
102123             }
102124
102125             newType = me.isNewItemEvent(item, e);
102126             if (newType === false) {
102127                 return false;
102128             }
102129
102130             if (
102131                 (me['onBeforeItem' + map[newType]](record, item, index, e) === false) ||
102132                 (me.fireEvent('beforeitem' + newType, me, record, item, index, e) === false) ||
102133                 (me['onItem' + map[newType]](record, item, index, e) === false)
102134             ) {
102135                 return false;
102136             }
102137
102138             me.fireEvent('item' + newType, me, record, item, index, e);
102139         }
102140         else {
102141             if (
102142                 (me.processContainerEvent(e) === false) ||
102143                 (me['onBeforeContainer' + map[type]](e) === false) ||
102144                 (me.fireEvent('beforecontainer' + type, me, e) === false) ||
102145                 (me['onContainer' + map[type]](e) === false)
102146             ) {
102147                 return false;
102148             }
102149
102150             me.fireEvent('container' + type, me, e);
102151         }
102152
102153         return true;
102154     },
102155
102156     isNewItemEvent: function (item, e) {
102157         var me = this,
102158             overItem = me.mouseOverItem,
102159             type = e.type;
102160
102161         switch (type) {
102162             case 'mouseover':
102163                 if (item === overItem) {
102164                     return false;
102165                 }
102166                 me.mouseOverItem = item;
102167                 return 'mouseenter';
102168
102169             case 'mouseout':
102170                 // If the currently mouseovered item contains the mouseover target, it's *NOT* a mouseleave
102171                 if (me.stillOverItem(e, overItem)) {
102172                     return false;
102173                 }
102174                 me.mouseOverItem = null;
102175                 return 'mouseleave';
102176         }
102177         return type;
102178     },
102179
102180     // private
102181     onItemMouseEnter: function(record, item, index, e) {
102182         if (this.trackOver) {
102183             this.highlightItem(item);
102184         }
102185     },
102186
102187     // private
102188     onItemMouseLeave : function(record, item, index, e) {
102189         if (this.trackOver) {
102190             this.clearHighlight();
102191         }
102192     },
102193
102194     // @private, template methods
102195     onItemMouseDown: Ext.emptyFn,
102196     onItemMouseUp: Ext.emptyFn,
102197     onItemFocus: Ext.emptyFn,
102198     onItemClick: Ext.emptyFn,
102199     onItemDblClick: Ext.emptyFn,
102200     onItemContextMenu: Ext.emptyFn,
102201     onItemKeyDown: Ext.emptyFn,
102202     onBeforeItemMouseDown: Ext.emptyFn,
102203     onBeforeItemMouseUp: Ext.emptyFn,
102204     onBeforeItemFocus: Ext.emptyFn,
102205     onBeforeItemMouseEnter: Ext.emptyFn,
102206     onBeforeItemMouseLeave: Ext.emptyFn,
102207     onBeforeItemClick: Ext.emptyFn,
102208     onBeforeItemDblClick: Ext.emptyFn,
102209     onBeforeItemContextMenu: Ext.emptyFn,
102210     onBeforeItemKeyDown: Ext.emptyFn,
102211
102212     // @private, template methods
102213     onContainerMouseDown: Ext.emptyFn,
102214     onContainerMouseUp: Ext.emptyFn,
102215     onContainerMouseOver: Ext.emptyFn,
102216     onContainerMouseOut: Ext.emptyFn,
102217     onContainerClick: Ext.emptyFn,
102218     onContainerDblClick: Ext.emptyFn,
102219     onContainerContextMenu: Ext.emptyFn,
102220     onContainerKeyDown: Ext.emptyFn,
102221     onBeforeContainerMouseDown: Ext.emptyFn,
102222     onBeforeContainerMouseUp: Ext.emptyFn,
102223     onBeforeContainerMouseOver: Ext.emptyFn,
102224     onBeforeContainerMouseOut: Ext.emptyFn,
102225     onBeforeContainerClick: Ext.emptyFn,
102226     onBeforeContainerDblClick: Ext.emptyFn,
102227     onBeforeContainerContextMenu: Ext.emptyFn,
102228     onBeforeContainerKeyDown: Ext.emptyFn,
102229
102230     /**
102231      * Highlights a given item in the DataView. This is called by the mouseover handler if {@link #overItemCls}
102232      * and {@link #trackOver} are configured, but can also be called manually by other code, for instance to
102233      * handle stepping through the list via keyboard navigation.
102234      * @param {HTMLElement} item The item to highlight
102235      */
102236     highlightItem: function(item) {
102237         var me = this;
102238         me.clearHighlight();
102239         me.highlightedItem = item;
102240         Ext.fly(item).addCls(me.overItemCls);
102241     },
102242
102243     /**
102244      * Un-highlights the currently highlighted item, if any.
102245      */
102246     clearHighlight: function() {
102247         var me = this,
102248             highlighted = me.highlightedItem;
102249
102250         if (highlighted) {
102251             Ext.fly(highlighted).removeCls(me.overItemCls);
102252             delete me.highlightedItem;
102253         }
102254     },
102255
102256     refresh: function() {
102257         var me = this;
102258         me.clearHighlight();
102259         me.callParent(arguments);
102260         if (!me.isFixedHeight()) {
102261             me.doComponentLayout();
102262         }
102263     }
102264 });
102265 /**
102266  * Component layout for {@link Ext.view.BoundList}. Handles constraining the height to the configured maxHeight.
102267  * @class Ext.layout.component.BoundList
102268  * @extends Ext.layout.component.Component
102269  * @private
102270  */
102271 Ext.define('Ext.layout.component.BoundList', {
102272     extend: 'Ext.layout.component.Component',
102273     alias: 'layout.boundlist',
102274
102275     type: 'component',
102276
102277     beforeLayout: function() {
102278         return this.callParent(arguments) || this.owner.refreshed > 0;
102279     },
102280
102281     onLayout : function(width, height) {
102282         var me = this,
102283             owner = me.owner,
102284             floating = owner.floating,
102285             el = owner.el,
102286             xy = el.getXY(),
102287             isNumber = Ext.isNumber,
102288             minWidth, maxWidth, minHeight, maxHeight,
102289             naturalWidth, naturalHeight, constrainedWidth, constrainedHeight, undef;
102290
102291         if (floating) {
102292             // Position offscreen so the natural width is not affected by the viewport's right edge
102293             el.setXY([-9999,-9999]);
102294         }
102295
102296         // Calculate initial layout
102297         me.setTargetSize(width, height);
102298
102299         // Handle min/maxWidth for auto-width
102300         if (!isNumber(width)) {
102301             minWidth = owner.minWidth;
102302             maxWidth = owner.maxWidth;
102303             if (isNumber(minWidth) || isNumber(maxWidth)) {
102304                 naturalWidth = el.getWidth();
102305                 if (naturalWidth < minWidth) {
102306                     constrainedWidth = minWidth;
102307                 }
102308                 else if (naturalWidth > maxWidth) {
102309                     constrainedWidth = maxWidth;
102310                 }
102311                 if (constrainedWidth) {
102312                     me.setTargetSize(constrainedWidth);
102313                 }
102314             }
102315         }
102316         // Handle min/maxHeight for auto-height
102317         if (!isNumber(height)) {
102318             minHeight = owner.minHeight;
102319             maxHeight = owner.maxHeight;
102320             if (isNumber(minHeight) || isNumber(maxHeight)) {
102321                 naturalHeight = el.getHeight();
102322                 if (naturalHeight < minHeight) {
102323                     constrainedHeight = minHeight;
102324                 }
102325                 else if (naturalHeight > maxHeight) {
102326                     constrainedHeight = maxHeight;
102327                 }
102328                 if (constrainedHeight) {
102329                     me.setTargetSize(undef, constrainedHeight);
102330                 }
102331             }
102332         }
102333
102334         if (floating) {
102335             // Restore position
102336             el.setXY(xy);
102337         }
102338     },
102339
102340     afterLayout: function() {
102341         var me = this,
102342             toolbar = me.owner.pagingToolbar;
102343         me.callParent();
102344         if (toolbar) {
102345             toolbar.doComponentLayout();
102346         }
102347     },
102348
102349     setTargetSize : function(width, height) {
102350         var me = this,
102351             owner = me.owner,
102352             listHeight = null,
102353             toolbar;
102354
102355         // Size the listEl
102356         if (Ext.isNumber(height)) {
102357             listHeight = height - owner.el.getFrameWidth('tb');
102358             toolbar = owner.pagingToolbar;
102359             if (toolbar) {
102360                 listHeight -= toolbar.getHeight();
102361             }
102362         }
102363         me.setElementSize(owner.listEl, null, listHeight);
102364
102365         me.callParent(arguments);
102366     }
102367
102368 });
102369
102370 /**
102371  * A simple class that renders text directly into a toolbar.
102372  *
102373  *     @example
102374  *     Ext.create('Ext.panel.Panel', {
102375  *         title: 'Panel with TextItem',
102376  *         width: 300,
102377  *         height: 200,
102378  *         tbar: [
102379  *             { xtype: 'tbtext', text: 'Sample Text Item' }
102380  *         ],
102381  *         renderTo: Ext.getBody()
102382  *     });
102383  *
102384  * @constructor
102385  * Creates a new TextItem
102386  * @param {Object} text A text string, or a config object containing a <tt>text</tt> property
102387  */
102388 Ext.define('Ext.toolbar.TextItem', {
102389     extend: 'Ext.toolbar.Item',
102390     requires: ['Ext.XTemplate'],
102391     alias: 'widget.tbtext',
102392     alternateClassName: 'Ext.Toolbar.TextItem',
102393
102394     /**
102395      * @cfg {String} text The text to be used as innerHTML (html tags are accepted)
102396      */
102397     text: '',
102398
102399     renderTpl: '{text}',
102400     //
102401     baseCls: Ext.baseCSSPrefix + 'toolbar-text',
102402
102403     onRender : function() {
102404         Ext.apply(this.renderData, {
102405             text: this.text
102406         });
102407         this.callParent(arguments);
102408     },
102409
102410     /**
102411      * Updates this item's text, setting the text to be used as innerHTML.
102412      * @param {String} t The text to display (html accepted).
102413      */
102414     setText : function(t) {
102415         if (this.rendered) {
102416             this.el.update(t);
102417             this.ownerCt.doLayout(); // In case an empty text item (centered at zero height) receives new text.
102418         } else {
102419             this.text = t;
102420         }
102421     }
102422 });
102423 /**
102424  * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
102425  * The trigger has no default action, so you must assign a function to implement the trigger click handler by overriding
102426  * {@link #onTriggerClick}. You can create a Trigger field directly, as it renders exactly like a combobox for which you
102427  * can provide a custom implementation.
102428  *
102429  * For example:
102430  *
102431  *     @example
102432  *     Ext.define('Ext.ux.CustomTrigger', {
102433  *         extend: 'Ext.form.field.Trigger',
102434  *         alias: 'widget.customtrigger',
102435  *
102436  *         // override onTriggerClick
102437  *         onTriggerClick: function() {
102438  *             Ext.Msg.alert('Status', 'You clicked my trigger!');
102439  *         }
102440  *     });
102441  *
102442  *     Ext.create('Ext.form.FormPanel', {
102443  *         title: 'Form with TriggerField',
102444  *         bodyPadding: 5,
102445  *         width: 350,
102446  *         renderTo: Ext.getBody(),
102447  *         items:[{
102448  *             xtype: 'customtrigger',
102449  *             fieldLabel: 'Sample Trigger',
102450  *             emptyText: 'click the trigger',
102451  *         }]
102452  *     });
102453  *
102454  * However, in general you will most likely want to use Trigger as the base class for a reusable component.
102455  * {@link Ext.form.field.Date} and {@link Ext.form.field.ComboBox} are perfect examples of this.
102456  */
102457 Ext.define('Ext.form.field.Trigger', {
102458     extend:'Ext.form.field.Text',
102459     alias: ['widget.triggerfield', 'widget.trigger'],
102460     requires: ['Ext.DomHelper', 'Ext.util.ClickRepeater', 'Ext.layout.component.field.Trigger'],
102461     alternateClassName: ['Ext.form.TriggerField', 'Ext.form.TwinTriggerField', 'Ext.form.Trigger'],
102462
102463     // note: {id} here is really {inputId}, but {cmpId} is available
102464     fieldSubTpl: [
102465         '<input id="{id}" type="{type}" ',
102466             '<tpl if="name">name="{name}" </tpl>',
102467             '<tpl if="size">size="{size}" </tpl>',
102468             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
102469             'class="{fieldCls} {typeCls}" autocomplete="off" />',
102470         '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
102471             '{triggerEl}',
102472             '<div class="{clearCls}" role="presentation"></div>',
102473         '</div>',
102474         {
102475             compiled: true,
102476             disableFormats: true
102477         }
102478     ],
102479
102480     /**
102481      * @cfg {String} triggerCls
102482      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
102483      * by default and triggerCls will be **appended** if specified.
102484      */
102485
102486     /**
102487      * @cfg {String} [triggerBaseCls='x-form-trigger']
102488      * The base CSS class that is always added to the trigger button. The {@link #triggerCls} will be appended in
102489      * addition to this class.
102490      */
102491     triggerBaseCls: Ext.baseCSSPrefix + 'form-trigger',
102492
102493     /**
102494      * @cfg {String} [triggerWrapCls='x-form-trigger-wrap']
102495      * The CSS class that is added to the div wrapping the trigger button(s).
102496      */
102497     triggerWrapCls: Ext.baseCSSPrefix + 'form-trigger-wrap',
102498
102499     /**
102500      * @cfg {Boolean} hideTrigger
102501      * true to hide the trigger element and display only the base text field
102502      */
102503     hideTrigger: false,
102504
102505     /**
102506      * @cfg {Boolean} editable
102507      * false to prevent the user from typing text directly into the field; the field can only have its value set via an
102508      * action invoked by the trigger.
102509      */
102510     editable: true,
102511
102512     /**
102513      * @cfg {Boolean} readOnly
102514      * true to prevent the user from changing the field, and hides the trigger. Supercedes the editable and hideTrigger
102515      * options if the value is true.
102516      */
102517     readOnly: false,
102518
102519     /**
102520      * @cfg {Boolean} [selectOnFocus=false]
102521      * true to select any existing text in the field immediately on focus. Only applies when
102522      * {@link #editable editable} = true
102523      */
102524
102525     /**
102526      * @cfg {Boolean} repeatTriggerClick
102527      * true to attach a {@link Ext.util.ClickRepeater click repeater} to the trigger.
102528      */
102529     repeatTriggerClick: false,
102530
102531
102532     /**
102533      * @hide
102534      * @method autoSize
102535      */
102536     autoSize: Ext.emptyFn,
102537     // private
102538     monitorTab: true,
102539     // private
102540     mimicing: false,
102541     // private
102542     triggerIndexRe: /trigger-index-(\d+)/,
102543
102544     componentLayout: 'triggerfield',
102545
102546     initComponent: function() {
102547         this.wrapFocusCls = this.triggerWrapCls + '-focus';
102548         this.callParent(arguments);
102549     },
102550
102551     // private
102552     onRender: function(ct, position) {
102553         var me = this,
102554             triggerCls,
102555             triggerBaseCls = me.triggerBaseCls,
102556             triggerWrapCls = me.triggerWrapCls,
102557             triggerConfigs = [],
102558             i;
102559
102560         // triggerCls is a synonym for trigger1Cls, so copy it.
102561         // TODO this trigger<n>Cls API design doesn't feel clean, especially where it butts up against the
102562         // single triggerCls config. Should rethink this, perhaps something more structured like a list of
102563         // trigger config objects that hold cls, handler, etc.
102564         if (!me.trigger1Cls) {
102565             me.trigger1Cls = me.triggerCls;
102566         }
102567
102568         // Create as many trigger elements as we have trigger<n>Cls configs, but always at least one
102569         for (i = 0; (triggerCls = me['trigger' + (i + 1) + 'Cls']) || i < 1; i++) {
102570             triggerConfigs.push({
102571                 cls: [Ext.baseCSSPrefix + 'trigger-index-' + i, triggerBaseCls, triggerCls].join(' '),
102572                 role: 'button'
102573             });
102574         }
102575         triggerConfigs[i - 1].cls += ' ' + triggerBaseCls + '-last';
102576
102577         /**
102578          * @property {Ext.Element} triggerWrap
102579          * A reference to the div element wrapping the trigger button(s). Only set after the field has been rendered.
102580          */
102581         me.addChildEls('triggerWrap');
102582
102583         Ext.applyIf(me.subTplData, {
102584             triggerWrapCls: triggerWrapCls,
102585             triggerEl: Ext.DomHelper.markup(triggerConfigs),
102586             clearCls: me.clearCls
102587         });
102588
102589         me.callParent(arguments);
102590
102591         /**
102592          * @property {Ext.CompositeElement} triggerEl
102593          * A composite of all the trigger button elements. Only set after the field has been rendered.
102594          */
102595         me.triggerEl = Ext.select('.' + triggerBaseCls, true, me.triggerWrap.dom);
102596
102597         me.doc = Ext.getDoc();
102598         me.initTrigger();
102599     },
102600
102601     onEnable: function() {
102602         this.callParent();
102603         this.triggerWrap.unmask();
102604     },
102605     
102606     onDisable: function() {
102607         this.callParent();
102608         this.triggerWrap.mask();
102609     },
102610     
102611     afterRender: function() {
102612         this.callParent();
102613         this.updateEditState();
102614         this.triggerEl.unselectable();
102615     },
102616
102617     updateEditState: function() {
102618         var me = this,
102619             inputEl = me.inputEl,
102620             triggerWrap = me.triggerWrap,
102621             noeditCls = Ext.baseCSSPrefix + 'trigger-noedit',
102622             displayed,
102623             readOnly;
102624
102625         if (me.rendered) {
102626             if (me.readOnly) {
102627                 inputEl.addCls(noeditCls);
102628                 readOnly = true;
102629                 displayed = false;
102630             } else {
102631                 if (me.editable) {
102632                     inputEl.removeCls(noeditCls);
102633                     readOnly = false;
102634                 } else {
102635                     inputEl.addCls(noeditCls);
102636                     readOnly = true;
102637                 }
102638                 displayed = !me.hideTrigger;
102639             }
102640
102641             triggerWrap.setDisplayed(displayed);
102642             inputEl.dom.readOnly = readOnly;
102643             me.doComponentLayout();
102644         }
102645     },
102646
102647     /**
102648      * Get the total width of the trigger button area. Only useful after the field has been rendered.
102649      * @return {Number} The trigger width
102650      */
102651     getTriggerWidth: function() {
102652         var me = this,
102653             triggerWrap = me.triggerWrap,
102654             totalTriggerWidth = 0;
102655         if (triggerWrap && !me.hideTrigger && !me.readOnly) {
102656             me.triggerEl.each(function(trigger) {
102657                 totalTriggerWidth += trigger.getWidth();
102658             });
102659             totalTriggerWidth += me.triggerWrap.getFrameWidth('lr');
102660         }
102661         return totalTriggerWidth;
102662     },
102663
102664     setHideTrigger: function(hideTrigger) {
102665         if (hideTrigger != this.hideTrigger) {
102666             this.hideTrigger = hideTrigger;
102667             this.updateEditState();
102668         }
102669     },
102670
102671     /**
102672      * Sets the editable state of this field. This method is the runtime equivalent of setting the 'editable' config
102673      * option at config time.
102674      * @param {Boolean} editable True to allow the user to directly edit the field text. If false is passed, the user
102675      * will only be able to modify the field using the trigger. Will also add a click event to the text field which
102676      * will call the trigger. 
102677      */
102678     setEditable: function(editable) {
102679         if (editable != this.editable) {
102680             this.editable = editable;
102681             this.updateEditState();
102682         }
102683     },
102684
102685     /**
102686      * Sets the read-only state of this field. This method is the runtime equivalent of setting the 'readOnly' config
102687      * option at config time.
102688      * @param {Boolean} readOnly True to prevent the user changing the field and explicitly hide the trigger. Setting
102689      * this to true will superceed settings editable and hideTrigger. Setting this to false will defer back to editable
102690      * and hideTrigger.
102691      */
102692     setReadOnly: function(readOnly) {
102693         if (readOnly != this.readOnly) {
102694             this.readOnly = readOnly;
102695             this.updateEditState();
102696         }
102697     },
102698
102699     // private
102700     initTrigger: function() {
102701         var me = this,
102702             triggerWrap = me.triggerWrap,
102703             triggerEl = me.triggerEl;
102704
102705         if (me.repeatTriggerClick) {
102706             me.triggerRepeater = Ext.create('Ext.util.ClickRepeater', triggerWrap, {
102707                 preventDefault: true,
102708                 handler: function(cr, e) {
102709                     me.onTriggerWrapClick(e);
102710                 }
102711             });
102712         } else {
102713             me.mon(me.triggerWrap, 'click', me.onTriggerWrapClick, me);
102714         }
102715
102716         triggerEl.addClsOnOver(me.triggerBaseCls + '-over');
102717         triggerEl.each(function(el, c, i) {
102718             el.addClsOnOver(me['trigger' + (i + 1) + 'Cls'] + '-over');
102719         });
102720         triggerEl.addClsOnClick(me.triggerBaseCls + '-click');
102721         triggerEl.each(function(el, c, i) {
102722             el.addClsOnClick(me['trigger' + (i + 1) + 'Cls'] + '-click');
102723         });
102724     },
102725
102726     // private
102727     onDestroy: function() {
102728         var me = this;
102729         Ext.destroyMembers(me, 'triggerRepeater', 'triggerWrap', 'triggerEl');
102730         delete me.doc;
102731         me.callParent();
102732     },
102733
102734     // private
102735     onFocus: function() {
102736         var me = this;
102737         me.callParent();
102738         if (!me.mimicing) {
102739             me.bodyEl.addCls(me.wrapFocusCls);
102740             me.mimicing = true;
102741             me.mon(me.doc, 'mousedown', me.mimicBlur, me, {
102742                 delay: 10
102743             });
102744             if (me.monitorTab) {
102745                 me.on('specialkey', me.checkTab, me);
102746             }
102747         }
102748     },
102749
102750     // private
102751     checkTab: function(me, e) {
102752         if (!this.ignoreMonitorTab && e.getKey() == e.TAB) {
102753             this.triggerBlur();
102754         }
102755     },
102756
102757     // private
102758     onBlur: Ext.emptyFn,
102759
102760     // private
102761     mimicBlur: function(e) {
102762         if (!this.isDestroyed && !this.bodyEl.contains(e.target) && this.validateBlur(e)) {
102763             this.triggerBlur();
102764         }
102765     },
102766
102767     // private
102768     triggerBlur: function() {
102769         var me = this;
102770         me.mimicing = false;
102771         me.mun(me.doc, 'mousedown', me.mimicBlur, me);
102772         if (me.monitorTab && me.inputEl) {
102773             me.un('specialkey', me.checkTab, me);
102774         }
102775         Ext.form.field.Trigger.superclass.onBlur.call(me);
102776         if (me.bodyEl) {
102777             me.bodyEl.removeCls(me.wrapFocusCls);
102778         }
102779     },
102780
102781     beforeBlur: Ext.emptyFn,
102782
102783     // private
102784     // This should be overridden by any subclass that needs to check whether or not the field can be blurred.
102785     validateBlur: function(e) {
102786         return true;
102787     },
102788
102789     // private
102790     // process clicks upon triggers.
102791     // determine which trigger index, and dispatch to the appropriate click handler
102792     onTriggerWrapClick: function(e) {
102793         var me = this,
102794             t = e && e.getTarget('.' + Ext.baseCSSPrefix + 'form-trigger', null),
102795             match = t && t.className.match(me.triggerIndexRe),
102796             idx,
102797             triggerClickMethod;
102798
102799         if (match && !me.readOnly) {
102800             idx = parseInt(match[1], 10);
102801             triggerClickMethod = me['onTrigger' + (idx + 1) + 'Click'] || me.onTriggerClick;
102802             if (triggerClickMethod) {
102803                 triggerClickMethod.call(me, e);
102804             }
102805         }
102806     },
102807
102808     /**
102809      * @method onTriggerClick
102810      * @protected
102811      * The function that should handle the trigger's click event. This method does nothing by default until overridden
102812      * by an implementing function. See Ext.form.field.ComboBox and Ext.form.field.Date for sample implementations.
102813      * @param {Ext.EventObject} e
102814      */
102815     onTriggerClick: Ext.emptyFn
102816
102817     /**
102818      * @cfg {Boolean} grow @hide
102819      */
102820     /**
102821      * @cfg {Number} growMin @hide
102822      */
102823     /**
102824      * @cfg {Number} growMax @hide
102825      */
102826 });
102827
102828 /**
102829  * An abstract class for fields that have a single trigger which opens a "picker" popup below the field, e.g. a combobox
102830  * menu list or a date picker. It provides a base implementation for toggling the picker's visibility when the trigger
102831  * is clicked, as well as keyboard navigation and some basic events. Sizing and alignment of the picker can be
102832  * controlled via the {@link #matchFieldWidth} and {@link #pickerAlign}/{@link #pickerOffset} config properties
102833  * respectively.
102834  *
102835  * You would not normally use this class directly, but instead use it as the parent class for a specific picker field
102836  * implementation. Subclasses must implement the {@link #createPicker} method to create a picker component appropriate
102837  * for the field.
102838  */
102839 Ext.define('Ext.form.field.Picker', {
102840     extend: 'Ext.form.field.Trigger',
102841     alias: 'widget.pickerfield',
102842     alternateClassName: 'Ext.form.Picker',
102843     requires: ['Ext.util.KeyNav'],
102844
102845     /**
102846      * @cfg {Boolean} matchFieldWidth
102847      * Whether the picker dropdown's width should be explicitly set to match the width of the field. Defaults to true.
102848      */
102849     matchFieldWidth: true,
102850
102851     /**
102852      * @cfg {String} pickerAlign
102853      * The {@link Ext.Element#alignTo alignment position} with which to align the picker. Defaults to "tl-bl?"
102854      */
102855     pickerAlign: 'tl-bl?',
102856
102857     /**
102858      * @cfg {Number[]} pickerOffset
102859      * An offset [x,y] to use in addition to the {@link #pickerAlign} when positioning the picker.
102860      * Defaults to undefined.
102861      */
102862
102863     /**
102864      * @cfg {String} openCls
102865      * A class to be added to the field's {@link #bodyEl} element when the picker is opened.
102866      * Defaults to 'x-pickerfield-open'.
102867      */
102868     openCls: Ext.baseCSSPrefix + 'pickerfield-open',
102869
102870     /**
102871      * @property {Boolean} isExpanded
102872      * True if the picker is currently expanded, false if not.
102873      */
102874
102875     /**
102876      * @cfg {Boolean} editable
102877      * False to prevent the user from typing text directly into the field; the field can only have its value set via
102878      * selecting a value from the picker. In this state, the picker can also be opened by clicking directly on the input
102879      * field itself.
102880      */
102881     editable: true,
102882
102883
102884     initComponent: function() {
102885         this.callParent();
102886
102887         // Custom events
102888         this.addEvents(
102889             /**
102890              * @event expand
102891              * Fires when the field's picker is expanded.
102892              * @param {Ext.form.field.Picker} field This field instance
102893              */
102894             'expand',
102895             /**
102896              * @event collapse
102897              * Fires when the field's picker is collapsed.
102898              * @param {Ext.form.field.Picker} field This field instance
102899              */
102900             'collapse',
102901             /**
102902              * @event select
102903              * Fires when a value is selected via the picker.
102904              * @param {Ext.form.field.Picker} field This field instance
102905              * @param {Object} value The value that was selected. The exact type of this value is dependent on
102906              * the individual field and picker implementations.
102907              */
102908             'select'
102909         );
102910     },
102911
102912
102913     initEvents: function() {
102914         var me = this;
102915         me.callParent();
102916
102917         // Add handlers for keys to expand/collapse the picker
102918         me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
102919             down: function() {
102920                 if (!me.isExpanded) {
102921                     // Don't call expand() directly as there may be additional processing involved before
102922                     // expanding, e.g. in the case of a ComboBox query.
102923                     me.onTriggerClick();
102924                 }
102925             },
102926             esc: me.collapse,
102927             scope: me,
102928             forceKeyDown: true
102929         });
102930
102931         // Non-editable allows opening the picker by clicking the field
102932         if (!me.editable) {
102933             me.mon(me.inputEl, 'click', me.onTriggerClick, me);
102934         }
102935
102936         // Disable native browser autocomplete
102937         if (Ext.isGecko) {
102938             me.inputEl.dom.setAttribute('autocomplete', 'off');
102939         }
102940     },
102941
102942
102943     /**
102944      * Expands this field's picker dropdown.
102945      */
102946     expand: function() {
102947         var me = this,
102948             bodyEl, picker, collapseIf;
102949
102950         if (me.rendered && !me.isExpanded && !me.isDestroyed) {
102951             bodyEl = me.bodyEl;
102952             picker = me.getPicker();
102953             collapseIf = me.collapseIf;
102954
102955             // show the picker and set isExpanded flag
102956             picker.show();
102957             me.isExpanded = true;
102958             me.alignPicker();
102959             bodyEl.addCls(me.openCls);
102960
102961             // monitor clicking and mousewheel
102962             me.mon(Ext.getDoc(), {
102963                 mousewheel: collapseIf,
102964                 mousedown: collapseIf,
102965                 scope: me
102966             });
102967             Ext.EventManager.onWindowResize(me.alignPicker, me);
102968             me.fireEvent('expand', me);
102969             me.onExpand();
102970         }
102971     },
102972
102973     onExpand: Ext.emptyFn,
102974
102975     /**
102976      * Aligns the picker to the input element
102977      * @protected
102978      */
102979     alignPicker: function() {
102980         var me = this,
102981             picker;
102982
102983         if (me.isExpanded) {
102984             picker = me.getPicker();
102985             if (me.matchFieldWidth) {
102986                 // Auto the height (it will be constrained by min and max width) unless there are no records to display.
102987                 picker.setSize(me.bodyEl.getWidth(), picker.store && picker.store.getCount() ? null : 0);
102988             }
102989             if (picker.isFloating()) {
102990                 me.doAlign();
102991             }
102992         }
102993     },
102994
102995     /**
102996      * Performs the alignment on the picker using the class defaults
102997      * @private
102998      */
102999     doAlign: function(){
103000         var me = this,
103001             picker = me.picker,
103002             aboveSfx = '-above',
103003             isAbove;
103004
103005         me.picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset);
103006         // add the {openCls}-above class if the picker was aligned above
103007         // the field due to hitting the bottom of the viewport
103008         isAbove = picker.el.getY() < me.inputEl.getY();
103009         me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx);
103010         picker[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
103011     },
103012
103013     /**
103014      * Collapses this field's picker dropdown.
103015      */
103016     collapse: function() {
103017         if (this.isExpanded && !this.isDestroyed) {
103018             var me = this,
103019                 openCls = me.openCls,
103020                 picker = me.picker,
103021                 doc = Ext.getDoc(),
103022                 collapseIf = me.collapseIf,
103023                 aboveSfx = '-above';
103024
103025             // hide the picker and set isExpanded flag
103026             picker.hide();
103027             me.isExpanded = false;
103028
103029             // remove the openCls
103030             me.bodyEl.removeCls([openCls, openCls + aboveSfx]);
103031             picker.el.removeCls(picker.baseCls + aboveSfx);
103032
103033             // remove event listeners
103034             doc.un('mousewheel', collapseIf, me);
103035             doc.un('mousedown', collapseIf, me);
103036             Ext.EventManager.removeResizeListener(me.alignPicker, me);
103037             me.fireEvent('collapse', me);
103038             me.onCollapse();
103039         }
103040     },
103041
103042     onCollapse: Ext.emptyFn,
103043
103044
103045     /**
103046      * @private
103047      * Runs on mousewheel and mousedown of doc to check to see if we should collapse the picker
103048      */
103049     collapseIf: function(e) {
103050         var me = this;
103051         if (!me.isDestroyed && !e.within(me.bodyEl, false, true) && !e.within(me.picker.el, false, true)) {
103052             me.collapse();
103053         }
103054     },
103055
103056     /**
103057      * Returns a reference to the picker component for this field, creating it if necessary by
103058      * calling {@link #createPicker}.
103059      * @return {Ext.Component} The picker component
103060      */
103061     getPicker: function() {
103062         var me = this;
103063         return me.picker || (me.picker = me.createPicker());
103064     },
103065
103066     /**
103067      * @method
103068      * Creates and returns the component to be used as this field's picker. Must be implemented by subclasses of Picker.
103069      * The current field should also be passed as a configuration option to the picker component as the pickerField
103070      * property.
103071      */
103072     createPicker: Ext.emptyFn,
103073
103074     /**
103075      * Handles the trigger click; by default toggles between expanding and collapsing the picker component.
103076      * @protected
103077      */
103078     onTriggerClick: function() {
103079         var me = this;
103080         if (!me.readOnly && !me.disabled) {
103081             if (me.isExpanded) {
103082                 me.collapse();
103083             } else {
103084                 me.expand();
103085             }
103086             me.inputEl.focus();
103087         }
103088     },
103089
103090     mimicBlur: function(e) {
103091         var me = this,
103092             picker = me.picker;
103093         // ignore mousedown events within the picker element
103094         if (!picker || !e.within(picker.el, false, true)) {
103095             me.callParent(arguments);
103096         }
103097     },
103098
103099     onDestroy : function(){
103100         var me = this,
103101             picker = me.picker;
103102
103103         Ext.EventManager.removeResizeListener(me.alignPicker, me);
103104         Ext.destroy(me.keyNav);
103105         if (picker) {
103106             delete picker.pickerField;
103107             picker.destroy();
103108         }
103109         me.callParent();
103110     }
103111
103112 });
103113
103114
103115 /**
103116  * A field with a pair of up/down spinner buttons. This class is not normally instantiated directly,
103117  * instead it is subclassed and the {@link #onSpinUp} and {@link #onSpinDown} methods are implemented
103118  * to handle when the buttons are clicked. A good example of this is the {@link Ext.form.field.Number}
103119  * field which uses the spinner to increment and decrement the field's value by its
103120  * {@link Ext.form.field.Number#step step} config value.
103121  *
103122  * For example:
103123  *
103124  *     @example
103125  *     Ext.define('Ext.ux.CustomSpinner', {
103126  *         extend: 'Ext.form.field.Spinner',
103127  *         alias: 'widget.customspinner',
103128  *
103129  *         // override onSpinUp (using step isn't neccessary)
103130  *         onSpinUp: function() {
103131  *             var me = this;
103132  *             if (!me.readOnly) {
103133  *                 var val = me.step; // set the default value to the step value
103134  *                 if(me.getValue() !== '') {
103135  *                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
103136  *                 }
103137  *                 me.setValue((val + me.step) + ' Pack');
103138  *             }
103139  *         },
103140  *
103141  *         // override onSpinDown
103142  *         onSpinDown: function() {
103143  *             var val, me = this;
103144  *             if (!me.readOnly) {
103145  *                 if(me.getValue() !== '') {
103146  *                     val = parseInt(me.getValue().slice(0, -5)); // gets rid of " Pack"
103147  *                 }
103148  *                 me.setValue((val - me.step) + ' Pack');
103149  *             }
103150  *         }
103151  *     });
103152  *
103153  *     Ext.create('Ext.form.FormPanel', {
103154  *         title: 'Form with SpinnerField',
103155  *         bodyPadding: 5,
103156  *         width: 350,
103157  *         renderTo: Ext.getBody(),
103158  *         items:[{
103159  *             xtype: 'customspinner',
103160  *             fieldLabel: 'How Much Beer?',
103161  *             step: 6
103162  *         }]
103163  *     });
103164  *
103165  * By default, pressing the up and down arrow keys will also trigger the onSpinUp and onSpinDown methods;
103166  * to prevent this, set `{@link #keyNavEnabled} = false`.
103167  */
103168 Ext.define('Ext.form.field.Spinner', {
103169     extend: 'Ext.form.field.Trigger',
103170     alias: 'widget.spinnerfield',
103171     alternateClassName: 'Ext.form.Spinner',
103172     requires: ['Ext.util.KeyNav'],
103173
103174     trigger1Cls: Ext.baseCSSPrefix + 'form-spinner-up',
103175     trigger2Cls: Ext.baseCSSPrefix + 'form-spinner-down',
103176
103177     /**
103178      * @cfg {Boolean} spinUpEnabled
103179      * Specifies whether the up spinner button is enabled. Defaults to true. To change this after the component is
103180      * created, use the {@link #setSpinUpEnabled} method.
103181      */
103182     spinUpEnabled: true,
103183
103184     /**
103185      * @cfg {Boolean} spinDownEnabled
103186      * Specifies whether the down spinner button is enabled. Defaults to true. To change this after the component is
103187      * created, use the {@link #setSpinDownEnabled} method.
103188      */
103189     spinDownEnabled: true,
103190
103191     /**
103192      * @cfg {Boolean} keyNavEnabled
103193      * Specifies whether the up and down arrow keys should trigger spinning up and down. Defaults to true.
103194      */
103195     keyNavEnabled: true,
103196
103197     /**
103198      * @cfg {Boolean} mouseWheelEnabled
103199      * Specifies whether the mouse wheel should trigger spinning up and down while the field has focus.
103200      * Defaults to true.
103201      */
103202     mouseWheelEnabled: true,
103203
103204     /**
103205      * @cfg {Boolean} repeatTriggerClick
103206      * Whether a {@link Ext.util.ClickRepeater click repeater} should be attached to the spinner buttons.
103207      * Defaults to true.
103208      */
103209     repeatTriggerClick: true,
103210
103211     /**
103212      * @method
103213      * @protected
103214      * This method is called when the spinner up button is clicked, or when the up arrow key is pressed if
103215      * {@link #keyNavEnabled} is true. Must be implemented by subclasses.
103216      */
103217     onSpinUp: Ext.emptyFn,
103218
103219     /**
103220      * @method
103221      * @protected
103222      * This method is called when the spinner down button is clicked, or when the down arrow key is pressed if
103223      * {@link #keyNavEnabled} is true. Must be implemented by subclasses.
103224      */
103225     onSpinDown: Ext.emptyFn,
103226
103227     initComponent: function() {
103228         this.callParent();
103229
103230         this.addEvents(
103231             /**
103232              * @event spin
103233              * Fires when the spinner is made to spin up or down.
103234              * @param {Ext.form.field.Spinner} this
103235              * @param {String} direction Either 'up' if spinning up, or 'down' if spinning down.
103236              */
103237             'spin',
103238
103239             /**
103240              * @event spinup
103241              * Fires when the spinner is made to spin up.
103242              * @param {Ext.form.field.Spinner} this
103243              */
103244             'spinup',
103245
103246             /**
103247              * @event spindown
103248              * Fires when the spinner is made to spin down.
103249              * @param {Ext.form.field.Spinner} this
103250              */
103251             'spindown'
103252         );
103253     },
103254
103255     /**
103256      * @private
103257      * Override.
103258      */
103259     onRender: function() {
103260         var me = this,
103261             triggers;
103262
103263         me.callParent(arguments);
103264         triggers = me.triggerEl;
103265
103266         /**
103267          * @property {Ext.Element} spinUpEl
103268          * The spinner up button element
103269          */
103270         me.spinUpEl = triggers.item(0);
103271         /**
103272          * @property {Ext.Element} spinDownEl
103273          * The spinner down button element
103274          */
103275         me.spinDownEl = triggers.item(1);
103276
103277         // Set initial enabled/disabled states
103278         me.setSpinUpEnabled(me.spinUpEnabled);
103279         me.setSpinDownEnabled(me.spinDownEnabled);
103280
103281         // Init up/down arrow keys
103282         if (me.keyNavEnabled) {
103283             me.spinnerKeyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
103284                 scope: me,
103285                 up: me.spinUp,
103286                 down: me.spinDown
103287             });
103288         }
103289
103290         // Init mouse wheel
103291         if (me.mouseWheelEnabled) {
103292             me.mon(me.bodyEl, 'mousewheel', me.onMouseWheel, me);
103293         }
103294     },
103295
103296     /**
103297      * @private
103298      * Override. Since the triggers are stacked, only measure the width of one of them.
103299      */
103300     getTriggerWidth: function() {
103301         return this.hideTrigger || this.readOnly ? 0 : this.spinUpEl.getWidth() + this.triggerWrap.getFrameWidth('lr');
103302     },
103303
103304     /**
103305      * @private
103306      * Handles the spinner up button clicks.
103307      */
103308     onTrigger1Click: function() {
103309         this.spinUp();
103310     },
103311
103312     /**
103313      * @private
103314      * Handles the spinner down button clicks.
103315      */
103316     onTrigger2Click: function() {
103317         this.spinDown();
103318     },
103319
103320     /**
103321      * Triggers the spinner to step up; fires the {@link #spin} and {@link #spinup} events and calls the
103322      * {@link #onSpinUp} method. Does nothing if the field is {@link #disabled} or if {@link #spinUpEnabled}
103323      * is false.
103324      */
103325     spinUp: function() {
103326         var me = this;
103327         if (me.spinUpEnabled && !me.disabled) {
103328             me.fireEvent('spin', me, 'up');
103329             me.fireEvent('spinup', me);
103330             me.onSpinUp();
103331         }
103332     },
103333
103334     /**
103335      * Triggers the spinner to step down; fires the {@link #spin} and {@link #spindown} events and calls the
103336      * {@link #onSpinDown} method. Does nothing if the field is {@link #disabled} or if {@link #spinDownEnabled}
103337      * is false.
103338      */
103339     spinDown: function() {
103340         var me = this;
103341         if (me.spinDownEnabled && !me.disabled) {
103342             me.fireEvent('spin', me, 'down');
103343             me.fireEvent('spindown', me);
103344             me.onSpinDown();
103345         }
103346     },
103347
103348     /**
103349      * Sets whether the spinner up button is enabled.
103350      * @param {Boolean} enabled true to enable the button, false to disable it.
103351      */
103352     setSpinUpEnabled: function(enabled) {
103353         var me = this,
103354             wasEnabled = me.spinUpEnabled;
103355         me.spinUpEnabled = enabled;
103356         if (wasEnabled !== enabled && me.rendered) {
103357             me.spinUpEl[enabled ? 'removeCls' : 'addCls'](me.trigger1Cls + '-disabled');
103358         }
103359     },
103360
103361     /**
103362      * Sets whether the spinner down button is enabled.
103363      * @param {Boolean} enabled true to enable the button, false to disable it.
103364      */
103365     setSpinDownEnabled: function(enabled) {
103366         var me = this,
103367             wasEnabled = me.spinDownEnabled;
103368         me.spinDownEnabled = enabled;
103369         if (wasEnabled !== enabled && me.rendered) {
103370             me.spinDownEl[enabled ? 'removeCls' : 'addCls'](me.trigger2Cls + '-disabled');
103371         }
103372     },
103373
103374     /**
103375      * @private
103376      * Handles mousewheel events on the field
103377      */
103378     onMouseWheel: function(e) {
103379         var me = this,
103380             delta;
103381         if (me.hasFocus) {
103382             delta = e.getWheelDelta();
103383             if (delta > 0) {
103384                 me.spinUp();
103385             }
103386             else if (delta < 0) {
103387                 me.spinDown();
103388             }
103389             e.stopEvent();
103390         }
103391     },
103392
103393     onDestroy: function() {
103394         Ext.destroyMembers(this, 'spinnerKeyNav', 'spinUpEl', 'spinDownEl');
103395         this.callParent();
103396     }
103397
103398 });
103399 /**
103400  * @docauthor Jason Johnston <jason@sencha.com>
103401  *
103402  * A numeric text field that provides automatic keystroke filtering to disallow non-numeric characters,
103403  * and numeric validation to limit the value to a range of valid numbers. The range of acceptable number
103404  * values can be controlled by setting the {@link #minValue} and {@link #maxValue} configs, and fractional
103405  * decimals can be disallowed by setting {@link #allowDecimals} to `false`.
103406  *
103407  * By default, the number field is also rendered with a set of up/down spinner buttons and has
103408  * up/down arrow key and mouse wheel event listeners attached for incrementing/decrementing the value by the
103409  * {@link #step} value. To hide the spinner buttons set `{@link #hideTrigger hideTrigger}:true`; to disable
103410  * the arrow key and mouse wheel handlers set `{@link #keyNavEnabled keyNavEnabled}:false` and
103411  * `{@link #mouseWheelEnabled mouseWheelEnabled}:false`. See the example below.
103412  *
103413  * # Example usage
103414  *
103415  *     @example
103416  *     Ext.create('Ext.form.Panel', {
103417  *         title: 'On The Wall',
103418  *         width: 300,
103419  *         bodyPadding: 10,
103420  *         renderTo: Ext.getBody(),
103421  *         items: [{
103422  *             xtype: 'numberfield',
103423  *             anchor: '100%',
103424  *             name: 'bottles',
103425  *             fieldLabel: 'Bottles of Beer',
103426  *             value: 99,
103427  *             maxValue: 99,
103428  *             minValue: 0
103429  *         }],
103430  *         buttons: [{
103431  *             text: 'Take one down, pass it around',
103432  *             handler: function() {
103433  *                 this.up('form').down('[name=bottles]').spinDown();
103434  *             }
103435  *         }]
103436  *     });
103437  *
103438  * # Removing UI Enhancements
103439  *
103440  *     @example
103441  *     Ext.create('Ext.form.Panel', {
103442  *         title: 'Personal Info',
103443  *         width: 300,
103444  *         bodyPadding: 10,
103445  *         renderTo: Ext.getBody(),
103446  *         items: [{
103447  *             xtype: 'numberfield',
103448  *             anchor: '100%',
103449  *             name: 'age',
103450  *             fieldLabel: 'Age',
103451  *             minValue: 0, //prevents negative numbers
103452  *
103453  *             // Remove spinner buttons, and arrow key and mouse wheel listeners
103454  *             hideTrigger: true,
103455  *             keyNavEnabled: false,
103456  *             mouseWheelEnabled: false
103457  *         }]
103458  *     });
103459  *
103460  * # Using Step
103461  *
103462  *     @example
103463  *     Ext.create('Ext.form.Panel', {
103464  *         renderTo: Ext.getBody(),
103465  *         title: 'Step',
103466  *         width: 300,
103467  *         bodyPadding: 10,
103468  *         items: [{
103469  *             xtype: 'numberfield',
103470  *             anchor: '100%',
103471  *             name: 'evens',
103472  *             fieldLabel: 'Even Numbers',
103473  *
103474  *             // Set step so it skips every other number
103475  *             step: 2,
103476  *             value: 0,
103477  *
103478  *             // Add change handler to force user-entered numbers to evens
103479  *             listeners: {
103480  *                 change: function(field, value) {
103481  *                     value = parseInt(value, 10);
103482  *                     field.setValue(value + value % 2);
103483  *                 }
103484  *             }
103485  *         }]
103486  *     });
103487  */
103488 Ext.define('Ext.form.field.Number', {
103489     extend:'Ext.form.field.Spinner',
103490     alias: 'widget.numberfield',
103491     alternateClassName: ['Ext.form.NumberField', 'Ext.form.Number'],
103492
103493     /**
103494      * @cfg {RegExp} stripCharsRe @hide
103495      */
103496     /**
103497      * @cfg {RegExp} maskRe @hide
103498      */
103499
103500     /**
103501      * @cfg {Boolean} allowDecimals
103502      * False to disallow decimal values
103503      */
103504     allowDecimals : true,
103505
103506     /**
103507      * @cfg {String} decimalSeparator
103508      * Character(s) to allow as the decimal separator
103509      */
103510     decimalSeparator : '.',
103511
103512     /**
103513      * @cfg {Number} decimalPrecision
103514      * The maximum precision to display after the decimal separator
103515      */
103516     decimalPrecision : 2,
103517
103518     /**
103519      * @cfg {Number} minValue
103520      * The minimum allowed value (defaults to Number.NEGATIVE_INFINITY). Will be used by the field's validation logic,
103521      * and for {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the down spinner button}.
103522      */
103523     minValue: Number.NEGATIVE_INFINITY,
103524
103525     /**
103526      * @cfg {Number} maxValue
103527      * The maximum allowed value (defaults to Number.MAX_VALUE). Will be used by the field's validation logic, and for
103528      * {@link Ext.form.field.Spinner#setSpinUpEnabled enabling/disabling the up spinner button}.
103529      */
103530     maxValue: Number.MAX_VALUE,
103531
103532     /**
103533      * @cfg {Number} step
103534      * Specifies a numeric interval by which the field's value will be incremented or decremented when the user invokes
103535      * the spinner.
103536      */
103537     step: 1,
103538
103539     /**
103540      * @cfg {String} minText
103541      * Error text to display if the minimum value validation fails.
103542      */
103543     minText : 'The minimum value for this field is {0}',
103544
103545     /**
103546      * @cfg {String} maxText
103547      * Error text to display if the maximum value validation fails.
103548      */
103549     maxText : 'The maximum value for this field is {0}',
103550
103551     /**
103552      * @cfg {String} nanText
103553      * Error text to display if the value is not a valid number. For example, this can happen if a valid character like
103554      * '.' or '-' is left in the field with no number.
103555      */
103556     nanText : '{0} is not a valid number',
103557
103558     /**
103559      * @cfg {String} negativeText
103560      * Error text to display if the value is negative and {@link #minValue} is set to 0. This is used instead of the
103561      * {@link #minText} in that circumstance only.
103562      */
103563     negativeText : 'The value cannot be negative',
103564
103565     /**
103566      * @cfg {String} baseChars
103567      * The base set of characters to evaluate as valid numbers.
103568      */
103569     baseChars : '0123456789',
103570
103571     /**
103572      * @cfg {Boolean} autoStripChars
103573      * True to automatically strip not allowed characters from the field.
103574      */
103575     autoStripChars: false,
103576
103577     initComponent: function() {
103578         var me = this,
103579             allowed;
103580
103581         me.callParent();
103582
103583         me.setMinValue(me.minValue);
103584         me.setMaxValue(me.maxValue);
103585
103586         // Build regexes for masking and stripping based on the configured options
103587         if (me.disableKeyFilter !== true) {
103588             allowed = me.baseChars + '';
103589             if (me.allowDecimals) {
103590                 allowed += me.decimalSeparator;
103591             }
103592             if (me.minValue < 0) {
103593                 allowed += '-';
103594             }
103595             allowed = Ext.String.escapeRegex(allowed);
103596             me.maskRe = new RegExp('[' + allowed + ']');
103597             if (me.autoStripChars) {
103598                 me.stripCharsRe = new RegExp('[^' + allowed + ']', 'gi');
103599             }
103600         }
103601     },
103602
103603     /**
103604      * Runs all of Number's validations and returns an array of any errors. Note that this first runs Text's
103605      * validations, so the returned array is an amalgamation of all field errors. The additional validations run test
103606      * that the value is a number, and that it is within the configured min and max values.
103607      * @param {Object} [value] The value to get errors for (defaults to the current field value)
103608      * @return {String[]} All validation errors for this field
103609      */
103610     getErrors: function(value) {
103611         var me = this,
103612             errors = me.callParent(arguments),
103613             format = Ext.String.format,
103614             num;
103615
103616         value = Ext.isDefined(value) ? value : this.processRawValue(this.getRawValue());
103617
103618         if (value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
103619              return errors;
103620         }
103621
103622         value = String(value).replace(me.decimalSeparator, '.');
103623
103624         if(isNaN(value)){
103625             errors.push(format(me.nanText, value));
103626         }
103627
103628         num = me.parseValue(value);
103629
103630         if (me.minValue === 0 && num < 0) {
103631             errors.push(this.negativeText);
103632         }
103633         else if (num < me.minValue) {
103634             errors.push(format(me.minText, me.minValue));
103635         }
103636
103637         if (num > me.maxValue) {
103638             errors.push(format(me.maxText, me.maxValue));
103639         }
103640
103641
103642         return errors;
103643     },
103644
103645     rawToValue: function(rawValue) {
103646         var value = this.fixPrecision(this.parseValue(rawValue));
103647         if (value === null) {
103648             value = rawValue || null;
103649         }
103650         return  value;
103651     },
103652
103653     valueToRaw: function(value) {
103654         var me = this,
103655             decimalSeparator = me.decimalSeparator;
103656         value = me.parseValue(value);
103657         value = me.fixPrecision(value);
103658         value = Ext.isNumber(value) ? value : parseFloat(String(value).replace(decimalSeparator, '.'));
103659         value = isNaN(value) ? '' : String(value).replace('.', decimalSeparator);
103660         return value;
103661     },
103662
103663     onChange: function() {
103664         var me = this,
103665             value = me.getValue(),
103666             valueIsNull = value === null;
103667
103668         me.callParent(arguments);
103669
103670         // Update the spinner buttons
103671         me.setSpinUpEnabled(valueIsNull || value < me.maxValue);
103672         me.setSpinDownEnabled(valueIsNull || value > me.minValue);
103673     },
103674
103675     /**
103676      * Replaces any existing {@link #minValue} with the new value.
103677      * @param {Number} value The minimum value
103678      */
103679     setMinValue : function(value) {
103680         this.minValue = Ext.Number.from(value, Number.NEGATIVE_INFINITY);
103681     },
103682
103683     /**
103684      * Replaces any existing {@link #maxValue} with the new value.
103685      * @param {Number} value The maximum value
103686      */
103687     setMaxValue: function(value) {
103688         this.maxValue = Ext.Number.from(value, Number.MAX_VALUE);
103689     },
103690
103691     // private
103692     parseValue : function(value) {
103693         value = parseFloat(String(value).replace(this.decimalSeparator, '.'));
103694         return isNaN(value) ? null : value;
103695     },
103696
103697     /**
103698      * @private
103699      */
103700     fixPrecision : function(value) {
103701         var me = this,
103702             nan = isNaN(value),
103703             precision = me.decimalPrecision;
103704
103705         if (nan || !value) {
103706             return nan ? '' : value;
103707         } else if (!me.allowDecimals || precision <= 0) {
103708             precision = 0;
103709         }
103710
103711         return parseFloat(Ext.Number.toFixed(parseFloat(value), precision));
103712     },
103713
103714     beforeBlur : function() {
103715         var me = this,
103716             v = me.parseValue(me.getRawValue());
103717
103718         if (!Ext.isEmpty(v)) {
103719             me.setValue(v);
103720         }
103721     },
103722
103723     onSpinUp: function() {
103724         var me = this;
103725         if (!me.readOnly) {
103726             me.setValue(Ext.Number.constrain(me.getValue() + me.step, me.minValue, me.maxValue));
103727         }
103728     },
103729
103730     onSpinDown: function() {
103731         var me = this;
103732         if (!me.readOnly) {
103733             me.setValue(Ext.Number.constrain(me.getValue() - me.step, me.minValue, me.maxValue));
103734         }
103735     }
103736 });
103737
103738 /**
103739  * As the number of records increases, the time required for the browser to render them increases. Paging is used to
103740  * reduce the amount of data exchanged with the client. Note: if there are more records/rows than can be viewed in the
103741  * available screen area, vertical scrollbars will be added.
103742  *
103743  * Paging is typically handled on the server side (see exception below). The client sends parameters to the server side,
103744  * which the server needs to interpret and then respond with the appropriate data.
103745  *
103746  * Ext.toolbar.Paging is a specialized toolbar that is bound to a {@link Ext.data.Store} and provides automatic
103747  * paging control. This Component {@link Ext.data.Store#load load}s blocks of data into the {@link #store} by passing
103748  * parameters used for paging criteria.
103749  *
103750  * {@img Ext.toolbar.Paging/Ext.toolbar.Paging.png Ext.toolbar.Paging component}
103751  *
103752  * Paging Toolbar is typically used as one of the Grid's toolbars:
103753  *
103754  *     @example
103755  *     var itemsPerPage = 2;   // set the number of items you want per page
103756  *
103757  *     var store = Ext.create('Ext.data.Store', {
103758  *         id:'simpsonsStore',
103759  *         autoLoad: false,
103760  *         fields:['name', 'email', 'phone'],
103761  *         pageSize: itemsPerPage, // items per page
103762  *         proxy: {
103763  *             type: 'ajax',
103764  *             url: 'pagingstore.js',  // url that will load data with respect to start and limit params
103765  *             reader: {
103766  *                 type: 'json',
103767  *                 root: 'items',
103768  *                 totalProperty: 'total'
103769  *             }
103770  *         }
103771  *     });
103772  *
103773  *     // specify segment of data you want to load using params
103774  *     store.load({
103775  *         params:{
103776  *             start:0,
103777  *             limit: itemsPerPage
103778  *         }
103779  *     });
103780  *
103781  *     Ext.create('Ext.grid.Panel', {
103782  *         title: 'Simpsons',
103783  *         store: store,
103784  *         columns: [
103785  *             { header: 'Name',  dataIndex: 'name' },
103786  *             { header: 'Email', dataIndex: 'email', flex: 1 },
103787  *             { header: 'Phone', dataIndex: 'phone' }
103788  *         ],
103789  *         width: 400,
103790  *         height: 125,
103791  *         dockedItems: [{
103792  *             xtype: 'pagingtoolbar',
103793  *             store: store,   // same store GridPanel is using
103794  *             dock: 'bottom',
103795  *             displayInfo: true
103796  *         }],
103797  *         renderTo: Ext.getBody()
103798  *     });
103799  *
103800  * To use paging, pass the paging requirements to the server when the store is first loaded.
103801  *
103802  *     store.load({
103803  *         params: {
103804  *             // specify params for the first page load if using paging
103805  *             start: 0,
103806  *             limit: myPageSize,
103807  *             // other params
103808  *             foo:   'bar'
103809  *         }
103810  *     });
103811  *
103812  * If using {@link Ext.data.Store#autoLoad store's autoLoad} configuration:
103813  *
103814  *     var myStore = Ext.create('Ext.data.Store', {
103815  *         {@link Ext.data.Store#autoLoad autoLoad}: {start: 0, limit: 25},
103816  *         ...
103817  *     });
103818  *
103819  * The packet sent back from the server would have this form:
103820  *
103821  *     {
103822  *         "success": true,
103823  *         "results": 2000,
103824  *         "rows": [ // ***Note:** this must be an Array
103825  *             { "id":  1, "name": "Bill", "occupation": "Gardener" },
103826  *             { "id":  2, "name":  "Ben", "occupation": "Horticulturalist" },
103827  *             ...
103828  *             { "id": 25, "name":  "Sue", "occupation": "Botanist" }
103829  *         ]
103830  *     }
103831  *
103832  * ## Paging with Local Data
103833  *
103834  * Paging can also be accomplished with local data using extensions:
103835  *
103836  *   - [Ext.ux.data.PagingStore][1]
103837  *   - Paging Memory Proxy (examples/ux/PagingMemoryProxy.js)
103838  *
103839  *    [1]: http://sencha.com/forum/showthread.php?t=71532
103840  */
103841 Ext.define('Ext.toolbar.Paging', {
103842     extend: 'Ext.toolbar.Toolbar',
103843     alias: 'widget.pagingtoolbar',
103844     alternateClassName: 'Ext.PagingToolbar',
103845     requires: ['Ext.toolbar.TextItem', 'Ext.form.field.Number'],
103846     /**
103847      * @cfg {Ext.data.Store} store (required)
103848      * The {@link Ext.data.Store} the paging toolbar should use as its data source.
103849      */
103850
103851     /**
103852      * @cfg {Boolean} displayInfo
103853      * true to display the displayMsg
103854      */
103855     displayInfo: false,
103856
103857     /**
103858      * @cfg {Boolean} prependButtons
103859      * true to insert any configured items _before_ the paging buttons.
103860      */
103861     prependButtons: false,
103862
103863     /**
103864      * @cfg {String} displayMsg
103865      * The paging status message to display. Note that this string is
103866      * formatted using the braced numbers {0}-{2} as tokens that are replaced by the values for start, end and total
103867      * respectively. These tokens should be preserved when overriding this string if showing those values is desired.
103868      */
103869     displayMsg : 'Displaying {0} - {1} of {2}',
103870
103871     /**
103872      * @cfg {String} emptyMsg
103873      * The message to display when no records are found.
103874      */
103875     emptyMsg : 'No data to display',
103876
103877     /**
103878      * @cfg {String} beforePageText
103879      * The text displayed before the input item.
103880      */
103881     beforePageText : 'Page',
103882
103883     /**
103884      * @cfg {String} afterPageText
103885      * Customizable piece of the default paging text. Note that this string is formatted using
103886      * {0} as a token that is replaced by the number of total pages. This token should be preserved when overriding this
103887      * string if showing the total page count is desired.
103888      */
103889     afterPageText : 'of {0}',
103890
103891     /**
103892      * @cfg {String} firstText
103893      * The quicktip text displayed for the first page button.
103894      * **Note**: quick tips must be initialized for the quicktip to show.
103895      */
103896     firstText : 'First Page',
103897
103898     /**
103899      * @cfg {String} prevText
103900      * The quicktip text displayed for the previous page button.
103901      * **Note**: quick tips must be initialized for the quicktip to show.
103902      */
103903     prevText : 'Previous Page',
103904
103905     /**
103906      * @cfg {String} nextText
103907      * The quicktip text displayed for the next page button.
103908      * **Note**: quick tips must be initialized for the quicktip to show.
103909      */
103910     nextText : 'Next Page',
103911
103912     /**
103913      * @cfg {String} lastText
103914      * The quicktip text displayed for the last page button.
103915      * **Note**: quick tips must be initialized for the quicktip to show.
103916      */
103917     lastText : 'Last Page',
103918
103919     /**
103920      * @cfg {String} refreshText
103921      * The quicktip text displayed for the Refresh button.
103922      * **Note**: quick tips must be initialized for the quicktip to show.
103923      */
103924     refreshText : 'Refresh',
103925
103926     /**
103927      * @cfg {Number} inputItemWidth
103928      * The width in pixels of the input field used to display and change the current page number.
103929      */
103930     inputItemWidth : 30,
103931
103932     /**
103933      * Gets the standard paging items in the toolbar
103934      * @private
103935      */
103936     getPagingItems: function() {
103937         var me = this;
103938
103939         return [{
103940             itemId: 'first',
103941             tooltip: me.firstText,
103942             overflowText: me.firstText,
103943             iconCls: Ext.baseCSSPrefix + 'tbar-page-first',
103944             disabled: true,
103945             handler: me.moveFirst,
103946             scope: me
103947         },{
103948             itemId: 'prev',
103949             tooltip: me.prevText,
103950             overflowText: me.prevText,
103951             iconCls: Ext.baseCSSPrefix + 'tbar-page-prev',
103952             disabled: true,
103953             handler: me.movePrevious,
103954             scope: me
103955         },
103956         '-',
103957         me.beforePageText,
103958         {
103959             xtype: 'numberfield',
103960             itemId: 'inputItem',
103961             name: 'inputItem',
103962             cls: Ext.baseCSSPrefix + 'tbar-page-number',
103963             allowDecimals: false,
103964             minValue: 1,
103965             hideTrigger: true,
103966             enableKeyEvents: true,
103967             selectOnFocus: true,
103968             submitValue: false,
103969             width: me.inputItemWidth,
103970             margins: '-1 2 3 2',
103971             listeners: {
103972                 scope: me,
103973                 keydown: me.onPagingKeyDown,
103974                 blur: me.onPagingBlur
103975             }
103976         },{
103977             xtype: 'tbtext',
103978             itemId: 'afterTextItem',
103979             text: Ext.String.format(me.afterPageText, 1)
103980         },
103981         '-',
103982         {
103983             itemId: 'next',
103984             tooltip: me.nextText,
103985             overflowText: me.nextText,
103986             iconCls: Ext.baseCSSPrefix + 'tbar-page-next',
103987             disabled: true,
103988             handler: me.moveNext,
103989             scope: me
103990         },{
103991             itemId: 'last',
103992             tooltip: me.lastText,
103993             overflowText: me.lastText,
103994             iconCls: Ext.baseCSSPrefix + 'tbar-page-last',
103995             disabled: true,
103996             handler: me.moveLast,
103997             scope: me
103998         },
103999         '-',
104000         {
104001             itemId: 'refresh',
104002             tooltip: me.refreshText,
104003             overflowText: me.refreshText,
104004             iconCls: Ext.baseCSSPrefix + 'tbar-loading',
104005             handler: me.doRefresh,
104006             scope: me
104007         }];
104008     },
104009
104010     initComponent : function(){
104011         var me = this,
104012             pagingItems = me.getPagingItems(),
104013             userItems   = me.items || me.buttons || [];
104014
104015         if (me.prependButtons) {
104016             me.items = userItems.concat(pagingItems);
104017         } else {
104018             me.items = pagingItems.concat(userItems);
104019         }
104020         delete me.buttons;
104021
104022         if (me.displayInfo) {
104023             me.items.push('->');
104024             me.items.push({xtype: 'tbtext', itemId: 'displayItem'});
104025         }
104026
104027         me.callParent();
104028
104029         me.addEvents(
104030             /**
104031              * @event change
104032              * Fires after the active page has been changed.
104033              * @param {Ext.toolbar.Paging} this
104034              * @param {Object} pageData An object that has these properties:
104035              *
104036              * - `total` : Number
104037              *
104038              *   The total number of records in the dataset as returned by the server
104039              *
104040              * - `currentPage` : Number
104041              *
104042              *   The current page number
104043              *
104044              * - `pageCount` : Number
104045              *
104046              *   The total number of pages (calculated from the total number of records in the dataset as returned by the
104047              *   server and the current {@link Ext.data.Store#pageSize pageSize})
104048              *
104049              * - `toRecord` : Number
104050              *
104051              *   The starting record index for the current page
104052              *
104053              * - `fromRecord` : Number
104054              *
104055              *   The ending record index for the current page
104056              */
104057             'change',
104058
104059             /**
104060              * @event beforechange
104061              * Fires just before the active page is changed. Return false to prevent the active page from being changed.
104062              * @param {Ext.toolbar.Paging} this
104063              * @param {Number} page The page number that will be loaded on change
104064              */
104065             'beforechange'
104066         );
104067         me.on('afterlayout', me.onLoad, me, {single: true});
104068
104069         me.bindStore(me.store || 'ext-empty-store', true);
104070     },
104071     // private
104072     updateInfo : function(){
104073         var me = this,
104074             displayItem = me.child('#displayItem'),
104075             store = me.store,
104076             pageData = me.getPageData(),
104077             count, msg;
104078
104079         if (displayItem) {
104080             count = store.getCount();
104081             if (count === 0) {
104082                 msg = me.emptyMsg;
104083             } else {
104084                 msg = Ext.String.format(
104085                     me.displayMsg,
104086                     pageData.fromRecord,
104087                     pageData.toRecord,
104088                     pageData.total
104089                 );
104090             }
104091             displayItem.setText(msg);
104092             me.doComponentLayout();
104093         }
104094     },
104095
104096     // private
104097     onLoad : function(){
104098         var me = this,
104099             pageData,
104100             currPage,
104101             pageCount,
104102             afterText;
104103
104104         if (!me.rendered) {
104105             return;
104106         }
104107
104108         pageData = me.getPageData();
104109         currPage = pageData.currentPage;
104110         pageCount = pageData.pageCount;
104111         afterText = Ext.String.format(me.afterPageText, isNaN(pageCount) ? 1 : pageCount);
104112
104113         me.child('#afterTextItem').setText(afterText);
104114         me.child('#inputItem').setValue(currPage);
104115         me.child('#first').setDisabled(currPage === 1);
104116         me.child('#prev').setDisabled(currPage === 1);
104117         me.child('#next').setDisabled(currPage === pageCount);
104118         me.child('#last').setDisabled(currPage === pageCount);
104119         me.child('#refresh').enable();
104120         me.updateInfo();
104121         me.fireEvent('change', me, pageData);
104122     },
104123
104124     // private
104125     getPageData : function(){
104126         var store = this.store,
104127             totalCount = store.getTotalCount();
104128
104129         return {
104130             total : totalCount,
104131             currentPage : store.currentPage,
104132             pageCount: Math.ceil(totalCount / store.pageSize),
104133             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
104134             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
104135
104136         };
104137     },
104138
104139     // private
104140     onLoadError : function(){
104141         if (!this.rendered) {
104142             return;
104143         }
104144         this.child('#refresh').enable();
104145     },
104146
104147     // private
104148     readPageFromInput : function(pageData){
104149         var v = this.child('#inputItem').getValue(),
104150             pageNum = parseInt(v, 10);
104151
104152         if (!v || isNaN(pageNum)) {
104153             this.child('#inputItem').setValue(pageData.currentPage);
104154             return false;
104155         }
104156         return pageNum;
104157     },
104158
104159     onPagingFocus : function(){
104160         this.child('#inputItem').select();
104161     },
104162
104163     //private
104164     onPagingBlur : function(e){
104165         var curPage = this.getPageData().currentPage;
104166         this.child('#inputItem').setValue(curPage);
104167     },
104168
104169     // private
104170     onPagingKeyDown : function(field, e){
104171         var me = this,
104172             k = e.getKey(),
104173             pageData = me.getPageData(),
104174             increment = e.shiftKey ? 10 : 1,
104175             pageNum;
104176
104177         if (k == e.RETURN) {
104178             e.stopEvent();
104179             pageNum = me.readPageFromInput(pageData);
104180             if (pageNum !== false) {
104181                 pageNum = Math.min(Math.max(1, pageNum), pageData.pageCount);
104182                 if(me.fireEvent('beforechange', me, pageNum) !== false){
104183                     me.store.loadPage(pageNum);
104184                 }
104185             }
104186         } else if (k == e.HOME || k == e.END) {
104187             e.stopEvent();
104188             pageNum = k == e.HOME ? 1 : pageData.pageCount;
104189             field.setValue(pageNum);
104190         } else if (k == e.UP || k == e.PAGEUP || k == e.DOWN || k == e.PAGEDOWN) {
104191             e.stopEvent();
104192             pageNum = me.readPageFromInput(pageData);
104193             if (pageNum) {
104194                 if (k == e.DOWN || k == e.PAGEDOWN) {
104195                     increment *= -1;
104196                 }
104197                 pageNum += increment;
104198                 if (pageNum >= 1 && pageNum <= pageData.pages) {
104199                     field.setValue(pageNum);
104200                 }
104201             }
104202         }
104203     },
104204
104205     // private
104206     beforeLoad : function(){
104207         if(this.rendered && this.refresh){
104208             this.refresh.disable();
104209         }
104210     },
104211
104212     // private
104213     doLoad : function(start){
104214         if(this.fireEvent('beforechange', this, o) !== false){
104215             this.store.load();
104216         }
104217     },
104218
104219     /**
104220      * Move to the first page, has the same effect as clicking the 'first' button.
104221      */
104222     moveFirst : function(){
104223         if (this.fireEvent('beforechange', this, 1) !== false){
104224             this.store.loadPage(1);
104225         }
104226     },
104227
104228     /**
104229      * Move to the previous page, has the same effect as clicking the 'previous' button.
104230      */
104231     movePrevious : function(){
104232         var me = this,
104233             prev = me.store.currentPage - 1;
104234
104235         if (prev > 0) {
104236             if (me.fireEvent('beforechange', me, prev) !== false) {
104237                 me.store.previousPage();
104238             }
104239         }
104240     },
104241
104242     /**
104243      * Move to the next page, has the same effect as clicking the 'next' button.
104244      */
104245     moveNext : function(){
104246         var me = this,
104247             total = me.getPageData().pageCount,
104248             next = me.store.currentPage + 1;
104249
104250         if (next <= total) {
104251             if (me.fireEvent('beforechange', me, next) !== false) {
104252                 me.store.nextPage();
104253             }
104254         }
104255     },
104256
104257     /**
104258      * Move to the last page, has the same effect as clicking the 'last' button.
104259      */
104260     moveLast : function(){
104261         var me = this,
104262             last = me.getPageData().pageCount;
104263
104264         if (me.fireEvent('beforechange', me, last) !== false) {
104265             me.store.loadPage(last);
104266         }
104267     },
104268
104269     /**
104270      * Refresh the current page, has the same effect as clicking the 'refresh' button.
104271      */
104272     doRefresh : function(){
104273         var me = this,
104274             current = me.store.currentPage;
104275
104276         if (me.fireEvent('beforechange', me, current) !== false) {
104277             me.store.loadPage(current);
104278         }
104279     },
104280
104281     /**
104282      * Binds the paging toolbar to the specified {@link Ext.data.Store}
104283      * @param {Ext.data.Store} store The store to bind to this toolbar
104284      * @param {Boolean} initial (Optional) true to not remove listeners
104285      */
104286     bindStore : function(store, initial){
104287         var me = this;
104288
104289         if (!initial && me.store) {
104290             if(store !== me.store && me.store.autoDestroy){
104291                 me.store.destroyStore();
104292             }else{
104293                 me.store.un('beforeload', me.beforeLoad, me);
104294                 me.store.un('load', me.onLoad, me);
104295                 me.store.un('exception', me.onLoadError, me);
104296             }
104297             if(!store){
104298                 me.store = null;
104299             }
104300         }
104301         if (store) {
104302             store = Ext.data.StoreManager.lookup(store);
104303             store.on({
104304                 scope: me,
104305                 beforeload: me.beforeLoad,
104306                 load: me.onLoad,
104307                 exception: me.onLoadError
104308             });
104309         }
104310         me.store = store;
104311     },
104312
104313     /**
104314      * Unbinds the paging toolbar from the specified {@link Ext.data.Store} **(deprecated)**
104315      * @param {Ext.data.Store} store The data store to unbind
104316      */
104317     unbind : function(store){
104318         this.bindStore(null);
104319     },
104320
104321     /**
104322      * Binds the paging toolbar to the specified {@link Ext.data.Store} **(deprecated)**
104323      * @param {Ext.data.Store} store The data store to bind
104324      */
104325     bind : function(store){
104326         this.bindStore(store);
104327     },
104328
104329     // private
104330     onDestroy : function(){
104331         this.bindStore(null);
104332         this.callParent();
104333     }
104334 });
104335
104336 /**
104337  * An internally used DataView for {@link Ext.form.field.ComboBox ComboBox}.
104338  */
104339 Ext.define('Ext.view.BoundList', {
104340     extend: 'Ext.view.View',
104341     alias: 'widget.boundlist',
104342     alternateClassName: 'Ext.BoundList',
104343     requires: ['Ext.layout.component.BoundList', 'Ext.toolbar.Paging'],
104344
104345     /**
104346      * @cfg {Number} pageSize
104347      * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed at the bottom of the list and store
104348      * queries will execute with page {@link Ext.data.Operation#start start} and
104349      * {@link Ext.data.Operation#limit limit} parameters. Defaults to `0`.
104350      */
104351     pageSize: 0,
104352
104353     /**
104354      * @property {Ext.toolbar.Paging} pagingToolbar
104355      * A reference to the PagingToolbar instance in this view. Only populated if {@link #pageSize} is greater
104356      * than zero and the BoundList has been rendered.
104357      */
104358
104359     // private overrides
104360     autoScroll: true,
104361     baseCls: Ext.baseCSSPrefix + 'boundlist',
104362     itemCls: Ext.baseCSSPrefix + 'boundlist-item',
104363     listItemCls: '',
104364     shadow: false,
104365     trackOver: true,
104366     refreshed: 0,
104367
104368     ariaRole: 'listbox',
104369
104370     componentLayout: 'boundlist',
104371
104372     renderTpl: ['<div id="{id}-listEl" class="list-ct"></div>'],
104373
104374     initComponent: function() {
104375         var me = this,
104376             baseCls = me.baseCls,
104377             itemCls = me.itemCls;
104378             
104379         me.selectedItemCls = baseCls + '-selected';
104380         me.overItemCls = baseCls + '-item-over';
104381         me.itemSelector = "." + itemCls;
104382
104383         if (me.floating) {
104384             me.addCls(baseCls + '-floating');
104385         }
104386
104387         if (!me.tpl) {
104388             // should be setting aria-posinset based on entire set of data
104389             // not filtered set
104390             me.tpl = Ext.create('Ext.XTemplate',
104391                 '<ul><tpl for=".">',
104392                     '<li role="option" class="' + itemCls + '">' + me.getInnerTpl(me.displayField) + '</li>',
104393                 '</tpl></ul>'
104394             );
104395         } else if (Ext.isString(me.tpl)) {
104396             me.tpl = Ext.create('Ext.XTemplate', me.tpl);
104397         }
104398
104399         if (me.pageSize) {
104400             me.pagingToolbar = me.createPagingToolbar();
104401         }
104402
104403         me.callParent();
104404
104405         me.addChildEls('listEl');
104406     },
104407
104408     createPagingToolbar: function() {
104409         return Ext.widget('pagingtoolbar', {
104410             pageSize: this.pageSize,
104411             store: this.store,
104412             border: false
104413         });
104414     },
104415
104416     onRender: function() {
104417         var me = this,
104418             toolbar = me.pagingToolbar;
104419         me.callParent(arguments);
104420         if (toolbar) {
104421             toolbar.render(me.el);
104422         }
104423     },
104424
104425     bindStore : function(store, initial) {
104426         var me = this,
104427             toolbar = me.pagingToolbar;
104428         me.callParent(arguments);
104429         if (toolbar) {
104430             toolbar.bindStore(store, initial);
104431         }
104432     },
104433
104434     getTargetEl: function() {
104435         return this.listEl || this.el;
104436     },
104437
104438     getInnerTpl: function(displayField) {
104439         return '{' + displayField + '}';
104440     },
104441
104442     refresh: function() {
104443         var me = this;
104444         me.callParent();
104445         if (me.isVisible()) {
104446             me.refreshed++;
104447             me.doComponentLayout();
104448             me.refreshed--;
104449         }
104450     },
104451
104452     initAria: function() {
104453         this.callParent();
104454
104455         var selModel = this.getSelectionModel(),
104456             mode     = selModel.getSelectionMode(),
104457             actionEl = this.getActionEl();
104458
104459         // TODO: subscribe to mode changes or allow the selModel to manipulate this attribute.
104460         if (mode !== 'SINGLE') {
104461             actionEl.dom.setAttribute('aria-multiselectable', true);
104462         }
104463     },
104464
104465     onDestroy: function() {
104466         Ext.destroyMembers(this, 'pagingToolbar', 'listEl');
104467         this.callParent();
104468     }
104469 });
104470
104471 /**
104472  * @class Ext.view.BoundListKeyNav
104473  * @extends Ext.util.KeyNav
104474  * A specialized {@link Ext.util.KeyNav} implementation for navigating a {@link Ext.view.BoundList} using
104475  * the keyboard. The up, down, pageup, pagedown, home, and end keys move the active highlight
104476  * through the list. The enter key invokes the selection model's select action using the highlighted item.
104477  */
104478 Ext.define('Ext.view.BoundListKeyNav', {
104479     extend: 'Ext.util.KeyNav',
104480     requires: 'Ext.view.BoundList',
104481
104482     /**
104483      * @cfg {Ext.view.BoundList} boundList (required)
104484      * The {@link Ext.view.BoundList} instance for which key navigation will be managed.
104485      */
104486
104487     constructor: function(el, config) {
104488         var me = this;
104489         me.boundList = config.boundList;
104490         me.callParent([el, Ext.apply({}, config, me.defaultHandlers)]);
104491     },
104492
104493     defaultHandlers: {
104494         up: function() {
104495             var me = this,
104496                 boundList = me.boundList,
104497                 allItems = boundList.all,
104498                 oldItem = boundList.highlightedItem,
104499                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
104500                 newItemIdx = oldItemIdx > 0 ? oldItemIdx - 1 : allItems.getCount() - 1; //wraps around
104501             me.highlightAt(newItemIdx);
104502         },
104503
104504         down: function() {
104505             var me = this,
104506                 boundList = me.boundList,
104507                 allItems = boundList.all,
104508                 oldItem = boundList.highlightedItem,
104509                 oldItemIdx = oldItem ? boundList.indexOf(oldItem) : -1,
104510                 newItemIdx = oldItemIdx < allItems.getCount() - 1 ? oldItemIdx + 1 : 0; //wraps around
104511             me.highlightAt(newItemIdx);
104512         },
104513
104514         pageup: function() {
104515             //TODO
104516         },
104517
104518         pagedown: function() {
104519             //TODO
104520         },
104521
104522         home: function() {
104523             this.highlightAt(0);
104524         },
104525
104526         end: function() {
104527             var me = this;
104528             me.highlightAt(me.boundList.all.getCount() - 1);
104529         },
104530
104531         enter: function(e) {
104532             this.selectHighlighted(e);
104533         }
104534     },
104535
104536     /**
104537      * Highlights the item at the given index.
104538      * @param {Number} index
104539      */
104540     highlightAt: function(index) {
104541         var boundList = this.boundList,
104542             item = boundList.all.item(index);
104543         if (item) {
104544             item = item.dom;
104545             boundList.highlightItem(item);
104546             boundList.getTargetEl().scrollChildIntoView(item, false);
104547         }
104548     },
104549
104550     /**
104551      * Triggers selection of the currently highlighted item according to the behavior of
104552      * the configured SelectionModel.
104553      */
104554     selectHighlighted: function(e) {
104555         var me = this,
104556             boundList = me.boundList,
104557             highlighted = boundList.highlightedItem,
104558             selModel = boundList.getSelectionModel();
104559         if (highlighted) {
104560             selModel.selectWithEvent(boundList.getRecord(highlighted), e);
104561         }
104562     }
104563
104564 });
104565 /**
104566  * @docauthor Jason Johnston <jason@sencha.com>
104567  *
104568  * A combobox control with support for autocomplete, remote loading, and many other features.
104569  *
104570  * A ComboBox is like a combination of a traditional HTML text `<input>` field and a `<select>`
104571  * field; the user is able to type freely into the field, and/or pick values from a dropdown selection
104572  * list. The user can input any value by default, even if it does not appear in the selection list;
104573  * to prevent free-form values and restrict them to items in the list, set {@link #forceSelection} to `true`.
104574  *
104575  * The selection list's options are populated from any {@link Ext.data.Store}, including remote
104576  * stores. The data items in the store are mapped to each option's displayed text and backing value via
104577  * the {@link #valueField} and {@link #displayField} configurations, respectively.
104578  *
104579  * If your store is not remote, i.e. it depends only on local data and is loaded up front, you should be
104580  * sure to set the {@link #queryMode} to `'local'`, as this will improve responsiveness for the user.
104581  *
104582  * # Example usage:
104583  *
104584  *     @example
104585  *     // The data store containing the list of states
104586  *     var states = Ext.create('Ext.data.Store', {
104587  *         fields: ['abbr', 'name'],
104588  *         data : [
104589  *             {"abbr":"AL", "name":"Alabama"},
104590  *             {"abbr":"AK", "name":"Alaska"},
104591  *             {"abbr":"AZ", "name":"Arizona"}
104592  *             //...
104593  *         ]
104594  *     });
104595  *
104596  *     // Create the combo box, attached to the states data store
104597  *     Ext.create('Ext.form.ComboBox', {
104598  *         fieldLabel: 'Choose State',
104599  *         store: states,
104600  *         queryMode: 'local',
104601  *         displayField: 'name',
104602  *         valueField: 'abbr',
104603  *         renderTo: Ext.getBody()
104604  *     });
104605  *
104606  * # Events
104607  *
104608  * To do something when something in ComboBox is selected, configure the select event:
104609  *
104610  *     var cb = Ext.create('Ext.form.ComboBox', {
104611  *         // all of your config options
104612  *         listeners:{
104613  *              scope: yourScope,
104614  *              'select': yourFunction
104615  *         }
104616  *     });
104617  *
104618  *     // Alternatively, you can assign events after the object is created:
104619  *     var cb = new Ext.form.field.ComboBox(yourOptions);
104620  *     cb.on('select', yourFunction, yourScope);
104621  *
104622  * # Multiple Selection
104623  *
104624  * ComboBox also allows selection of multiple items from the list; to enable multi-selection set the
104625  * {@link #multiSelect} config to `true`.
104626  */
104627 Ext.define('Ext.form.field.ComboBox', {
104628     extend:'Ext.form.field.Picker',
104629     requires: ['Ext.util.DelayedTask', 'Ext.EventObject', 'Ext.view.BoundList', 'Ext.view.BoundListKeyNav', 'Ext.data.StoreManager'],
104630     alternateClassName: 'Ext.form.ComboBox',
104631     alias: ['widget.combobox', 'widget.combo'],
104632
104633     /**
104634      * @cfg {String} [triggerCls='x-form-arrow-trigger']
104635      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
104636      * by default and `triggerCls` will be **appended** if specified.
104637      */
104638     triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
104639
104640     /**
104641      * @private
104642      * @cfg {String}
104643      * CSS class used to find the {@link #hiddenDataEl}
104644      */
104645     hiddenDataCls: Ext.baseCSSPrefix + 'hide-display ' + Ext.baseCSSPrefix + 'form-data-hidden',
104646
104647     /**
104648      * @override
104649      */
104650     fieldSubTpl: [
104651         '<div class="{hiddenDataCls}" role="presentation"></div>',
104652         '<input id="{id}" type="{type}" ',
104653             '<tpl if="size">size="{size}" </tpl>',
104654             '<tpl if="tabIdx">tabIndex="{tabIdx}" </tpl>',
104655             'class="{fieldCls} {typeCls}" autocomplete="off" />',
104656         '<div id="{cmpId}-triggerWrap" class="{triggerWrapCls}" role="presentation">',
104657             '{triggerEl}',
104658             '<div class="{clearCls}" role="presentation"></div>',
104659         '</div>',
104660         {
104661             compiled: true,
104662             disableFormats: true
104663         }
104664     ],
104665
104666     getSubTplData: function(){
104667         var me = this;
104668         Ext.applyIf(me.subTplData, {
104669             hiddenDataCls: me.hiddenDataCls
104670         });
104671         return me.callParent(arguments);
104672     },
104673
104674     afterRender: function(){
104675         var me = this;
104676         me.callParent(arguments);
104677         me.setHiddenValue(me.value);
104678     },
104679
104680     /**
104681      * @cfg {Ext.data.Store/Array} store
104682      * The data source to which this combo is bound. Acceptable values for this property are:
104683      *
104684      *   - **any {@link Ext.data.Store Store} subclass**
104685      *   - **an Array** : Arrays will be converted to a {@link Ext.data.Store} internally, automatically generating
104686      *     {@link Ext.data.Field#name field names} to work with all data components.
104687      *
104688      *     - **1-dimensional array** : (e.g., `['Foo','Bar']`)
104689      *
104690      *       A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
104691      *       {@link #valueField} and {@link #displayField})
104692      *
104693      *     - **2-dimensional array** : (e.g., `[['f','Foo'],['b','Bar']]`)
104694      *
104695      *       For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
104696      *       {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.
104697      *
104698      * See also {@link #queryMode}.
104699      */
104700
104701     /**
104702      * @cfg {Boolean} multiSelect
104703      * If set to `true`, allows the combo field to hold more than one value at a time, and allows selecting multiple
104704      * items from the dropdown list. The combo's text field will show all selected values separated by the
104705      * {@link #delimiter}.
104706      */
104707     multiSelect: false,
104708
104709     /**
104710      * @cfg {String} delimiter
104711      * The character(s) used to separate the {@link #displayField display values} of multiple selected items when
104712      * `{@link #multiSelect} = true`.
104713      */
104714     delimiter: ', ',
104715
104716     /**
104717      * @cfg {String} displayField
104718      * The underlying {@link Ext.data.Field#name data field name} to bind to this ComboBox.
104719      *
104720      * See also `{@link #valueField}`.
104721      */
104722     displayField: 'text',
104723
104724     /**
104725      * @cfg {String} valueField (required)
104726      * The underlying {@link Ext.data.Field#name data value name} to bind to this ComboBox (defaults to match
104727      * the value of the {@link #displayField} config).
104728      *
104729      * **Note**: use of a `valueField` requires the user to make a selection in order for a value to be mapped. See also
104730      * `{@link #displayField}`.
104731      */
104732
104733     /**
104734      * @cfg {String} triggerAction
104735      * The action to execute when the trigger is clicked.
104736      *
104737      *   - **`'all'`** :
104738      *
104739      *     {@link #doQuery run the query} specified by the `{@link #allQuery}` config option
104740      *
104741      *   - **`'query'`** :
104742      *
104743      *     {@link #doQuery run the query} using the {@link Ext.form.field.Base#getRawValue raw value}.
104744      *
104745      * See also `{@link #queryParam}`.
104746      */
104747     triggerAction: 'all',
104748
104749     /**
104750      * @cfg {String} allQuery
104751      * The text query to send to the server to return all records for the list with no filtering
104752      */
104753     allQuery: '',
104754
104755     /**
104756      * @cfg {String} queryParam
104757      * Name of the parameter used by the Store to pass the typed string when the ComboBox is configured with
104758      * `{@link #queryMode}: 'remote'`. If explicitly set to a falsy value it will not be sent.
104759      */
104760     queryParam: 'query',
104761
104762     /**
104763      * @cfg {String} queryMode
104764      * The mode in which the ComboBox uses the configured Store. Acceptable values are:
104765      *
104766      *   - **`'remote'`** :
104767      *
104768      *     In `queryMode: 'remote'`, the ComboBox loads its Store dynamically based upon user interaction.
104769      *
104770      *     This is typically used for "autocomplete" type inputs, and after the user finishes typing, the Store is {@link
104771      *     Ext.data.Store#load load}ed.
104772      *
104773      *     A parameter containing the typed string is sent in the load request. The default parameter name for the input
104774      *     string is `query`, but this can be configured using the {@link #queryParam} config.
104775      *
104776      *     In `queryMode: 'remote'`, the Store may be configured with `{@link Ext.data.Store#remoteFilter remoteFilter}:
104777      *     true`, and further filters may be _programatically_ added to the Store which are then passed with every load
104778      *     request which allows the server to further refine the returned dataset.
104779      *
104780      *     Typically, in an autocomplete situation, {@link #hideTrigger} is configured `true` because it has no meaning for
104781      *     autocomplete.
104782      *
104783      *   - **`'local'`** :
104784      *
104785      *     ComboBox loads local data
104786      *
104787      *         var combo = new Ext.form.field.ComboBox({
104788      *             renderTo: document.body,
104789      *             queryMode: 'local',
104790      *             store: new Ext.data.ArrayStore({
104791      *                 id: 0,
104792      *                 fields: [
104793      *                     'myId',  // numeric value is the key
104794      *                     'displayText'
104795      *                 ],
104796      *                 data: [[1, 'item1'], [2, 'item2']]  // data is local
104797      *             }),
104798      *             valueField: 'myId',
104799      *             displayField: 'displayText',
104800      *             triggerAction: 'all'
104801      *         });
104802      */
104803     queryMode: 'remote',
104804
104805     queryCaching: true,
104806
104807     /**
104808      * @cfg {Number} pageSize
104809      * If greater than `0`, a {@link Ext.toolbar.Paging} is displayed in the footer of the dropdown list and the
104810      * {@link #doQuery filter queries} will execute with page start and {@link Ext.view.BoundList#pageSize limit}
104811      * parameters. Only applies when `{@link #queryMode} = 'remote'`.
104812      */
104813     pageSize: 0,
104814
104815     /**
104816      * @cfg {Number} queryDelay
104817      * The length of time in milliseconds to delay between the start of typing and sending the query to filter the
104818      * dropdown list (defaults to `500` if `{@link #queryMode} = 'remote'` or `10` if `{@link #queryMode} = 'local'`)
104819      */
104820
104821     /**
104822      * @cfg {Number} minChars
104823      * The minimum number of characters the user must type before autocomplete and {@link #typeAhead} activate (defaults
104824      * to `4` if `{@link #queryMode} = 'remote'` or `0` if `{@link #queryMode} = 'local'`, does not apply if
104825      * `{@link Ext.form.field.Trigger#editable editable} = false`).
104826      */
104827
104828     /**
104829      * @cfg {Boolean} autoSelect
104830      * `true` to automatically highlight the first result gathered by the data store in the dropdown list when it is
104831      * opened. A false value would cause nothing in the list to be highlighted automatically, so
104832      * the user would have to manually highlight an item before pressing the enter or {@link #selectOnTab tab} key to
104833      * select it (unless the value of ({@link #typeAhead}) were true), or use the mouse to select a value.
104834      */
104835     autoSelect: true,
104836
104837     /**
104838      * @cfg {Boolean} typeAhead
104839      * `true` to populate and autoselect the remainder of the text being typed after a configurable delay
104840      * ({@link #typeAheadDelay}) if it matches a known value.
104841      */
104842     typeAhead: false,
104843
104844     /**
104845      * @cfg {Number} typeAheadDelay
104846      * The length of time in milliseconds to wait until the typeahead text is displayed if `{@link #typeAhead} = true`
104847      */
104848     typeAheadDelay: 250,
104849
104850     /**
104851      * @cfg {Boolean} selectOnTab
104852      * Whether the Tab key should select the currently highlighted item.
104853      */
104854     selectOnTab: true,
104855
104856     /**
104857      * @cfg {Boolean} forceSelection
104858      * `true` to restrict the selected value to one of the values in the list, `false` to allow the user to set
104859      * arbitrary text into the field.
104860      */
104861     forceSelection: false,
104862
104863     /**
104864      * @cfg {String} valueNotFoundText
104865      * When using a name/value combo, if the value passed to setValue is not found in the store, valueNotFoundText will
104866      * be displayed as the field text if defined. If this default text is used, it means there
104867      * is no value set and no validation will occur on this field.
104868      */
104869
104870     /**
104871      * @property {String} lastQuery
104872      * The value of the match string used to filter the store. Delete this property to force a requery. Example use:
104873      *
104874      *     var combo = new Ext.form.field.ComboBox({
104875      *         ...
104876      *         queryMode: 'remote',
104877      *         listeners: {
104878      *             // delete the previous query in the beforequery event or set
104879      *             // combo.lastQuery = null (this will reload the store the next time it expands)
104880      *             beforequery: function(qe){
104881      *                 delete qe.combo.lastQuery;
104882      *             }
104883      *         }
104884      *     });
104885      *
104886      * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used configure the
104887      * combo with `lastQuery=''`. Example use:
104888      *
104889      *     var combo = new Ext.form.field.ComboBox({
104890      *         ...
104891      *         queryMode: 'local',
104892      *         triggerAction: 'all',
104893      *         lastQuery: ''
104894      *     });
104895      */
104896
104897     /**
104898      * @cfg {Object} defaultListConfig
104899      * Set of options that will be used as defaults for the user-configured {@link #listConfig} object.
104900      */
104901     defaultListConfig: {
104902         emptyText: '',
104903         loadingText: 'Loading...',
104904         loadingHeight: 70,
104905         minWidth: 70,
104906         maxHeight: 300,
104907         shadow: 'sides'
104908     },
104909
104910     /**
104911      * @cfg {String/HTMLElement/Ext.Element} transform
104912      * The id, DOM node or {@link Ext.Element} of an existing HTML `<select>` element to convert into a ComboBox. The
104913      * target select's options will be used to build the options in the ComboBox dropdown; a configured {@link #store}
104914      * will take precedence over this.
104915      */
104916
104917     /**
104918      * @cfg {Object} listConfig
104919      * An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s constructor.
104920      * Any configuration that is valid for BoundList can be included. Some of the more useful ones are:
104921      *
104922      *   - {@link Ext.view.BoundList#cls} - defaults to empty
104923      *   - {@link Ext.view.BoundList#emptyText} - defaults to empty string
104924      *   - {@link Ext.view.BoundList#itemSelector} - defaults to the value defined in BoundList
104925      *   - {@link Ext.view.BoundList#loadingText} - defaults to `'Loading...'`
104926      *   - {@link Ext.view.BoundList#minWidth} - defaults to `70`
104927      *   - {@link Ext.view.BoundList#maxWidth} - defaults to `undefined`
104928      *   - {@link Ext.view.BoundList#maxHeight} - defaults to `300`
104929      *   - {@link Ext.view.BoundList#resizable} - defaults to `false`
104930      *   - {@link Ext.view.BoundList#shadow} - defaults to `'sides'`
104931      *   - {@link Ext.view.BoundList#width} - defaults to `undefined` (automatically set to the width of the ComboBox
104932      *     field if {@link #matchFieldWidth} is true)
104933      */
104934
104935     //private
104936     ignoreSelection: 0,
104937
104938     initComponent: function() {
104939         var me = this,
104940             isDefined = Ext.isDefined,
104941             store = me.store,
104942             transform = me.transform,
104943             transformSelect, isLocalMode;
104944
104945         Ext.applyIf(me.renderSelectors, {
104946             hiddenDataEl: '.' + me.hiddenDataCls.split(' ').join('.')
104947         });
104948         
104949
104950         this.addEvents(
104951             /**
104952              * @event beforequery
104953              * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's cancel
104954              * property to true.
104955              *
104956              * @param {Object} queryEvent An object that has these properties:
104957              *
104958              *   - `combo` : Ext.form.field.ComboBox
104959              *
104960              *     This combo box
104961              *
104962              *   - `query` : String
104963              *
104964              *     The query string
104965              *
104966              *   - `forceAll` : Boolean
104967              *
104968              *     True to force "all" query
104969              *
104970              *   - `cancel` : Boolean
104971              *
104972              *     Set to true to cancel the query
104973              */
104974             'beforequery',
104975
104976             /**
104977              * @event select
104978              * Fires when at least one list item is selected.
104979              * @param {Ext.form.field.ComboBox} combo This combo box
104980              * @param {Array} records The selected records
104981              */
104982             'select',
104983
104984             /**
104985              * @event beforeselect
104986              * Fires before the selected item is added to the collection
104987              * @param {Ext.form.field.ComboBox} combo This combo box
104988              * @param {Ext.data.Record} record The selected record
104989              * @param {Number} index The index of the selected record
104990              */
104991             'beforeselect',
104992
104993             /**
104994              * @event beforedeselect
104995              * Fires before the deselected item is removed from the collection
104996              * @param {Ext.form.field.ComboBox} combo This combo box
104997              * @param {Ext.data.Record} record The deselected record
104998              * @param {Number} index The index of the deselected record
104999              */
105000             'beforedeselect'
105001         );
105002
105003         // Build store from 'transform' HTML select element's options
105004         if (transform) {
105005             transformSelect = Ext.getDom(transform);
105006             if (transformSelect) {
105007                 store = Ext.Array.map(Ext.Array.from(transformSelect.options), function(option) {
105008                     return [option.value, option.text];
105009                 });
105010                 if (!me.name) {
105011                     me.name = transformSelect.name;
105012                 }
105013                 if (!('value' in me)) {
105014                     me.value = transformSelect.value;
105015                 }
105016             }
105017         }
105018
105019         me.bindStore(store || 'ext-empty-store', true);
105020         store = me.store;
105021         if (store.autoCreated) {
105022             me.queryMode = 'local';
105023             me.valueField = me.displayField = 'field1';
105024             if (!store.expanded) {
105025                 me.displayField = 'field2';
105026             }
105027         }
105028
105029
105030         if (!isDefined(me.valueField)) {
105031             me.valueField = me.displayField;
105032         }
105033
105034         isLocalMode = me.queryMode === 'local';
105035         if (!isDefined(me.queryDelay)) {
105036             me.queryDelay = isLocalMode ? 10 : 500;
105037         }
105038         if (!isDefined(me.minChars)) {
105039             me.minChars = isLocalMode ? 0 : 4;
105040         }
105041
105042         if (!me.displayTpl) {
105043             me.displayTpl = Ext.create('Ext.XTemplate',
105044                 '<tpl for=".">' +
105045                     '{[typeof values === "string" ? values : values["' + me.displayField + '"]]}' +
105046                     '<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +
105047                 '</tpl>'
105048             );
105049         } else if (Ext.isString(me.displayTpl)) {
105050             me.displayTpl = Ext.create('Ext.XTemplate', me.displayTpl);
105051         }
105052
105053         me.callParent();
105054
105055         me.doQueryTask = Ext.create('Ext.util.DelayedTask', me.doRawQuery, me);
105056
105057         // store has already been loaded, setValue
105058         if (me.store.getCount() > 0) {
105059             me.setValue(me.value);
105060         }
105061
105062         // render in place of 'transform' select
105063         if (transformSelect) {
105064             me.render(transformSelect.parentNode, transformSelect);
105065             Ext.removeNode(transformSelect);
105066             delete me.renderTo;
105067         }
105068     },
105069
105070     /**
105071      * Returns the store associated with this ComboBox.
105072      * @return {Ext.data.Store} The store
105073      */
105074     getStore : function(){
105075         return this.store;
105076     },
105077
105078     beforeBlur: function() {
105079         this.doQueryTask.cancel();
105080         this.assertValue();
105081     },
105082
105083     // private
105084     assertValue: function() {
105085         var me = this,
105086             value = me.getRawValue(),
105087             rec;
105088
105089         if (me.forceSelection) {
105090             if (me.multiSelect) {
105091                 // For multiselect, check that the current displayed value matches the current
105092                 // selection, if it does not then revert to the most recent selection.
105093                 if (value !== me.getDisplayValue()) {
105094                     me.setValue(me.lastSelection);
105095                 }
105096             } else {
105097                 // For single-select, match the displayed value to a record and select it,
105098                 // if it does not match a record then revert to the most recent selection.
105099                 rec = me.findRecordByDisplay(value);
105100                 if (rec) {
105101                     me.select(rec);
105102                 } else {
105103                     me.setValue(me.lastSelection);
105104                 }
105105             }
105106         }
105107         me.collapse();
105108     },
105109
105110     onTypeAhead: function() {
105111         var me = this,
105112             displayField = me.displayField,
105113             record = me.store.findRecord(displayField, me.getRawValue()),
105114             boundList = me.getPicker(),
105115             newValue, len, selStart;
105116
105117         if (record) {
105118             newValue = record.get(displayField);
105119             len = newValue.length;
105120             selStart = me.getRawValue().length;
105121
105122             boundList.highlightItem(boundList.getNode(record));
105123
105124             if (selStart !== 0 && selStart !== len) {
105125                 me.setRawValue(newValue);
105126                 me.selectText(selStart, newValue.length);
105127             }
105128         }
105129     },
105130
105131     // invoked when a different store is bound to this combo
105132     // than the original
105133     resetToDefault: function() {
105134
105135     },
105136
105137     bindStore: function(store, initial) {
105138         var me = this,
105139             oldStore = me.store;
105140
105141         // this code directly accesses this.picker, bc invoking getPicker
105142         // would create it when we may be preping to destroy it
105143         if (oldStore && !initial) {
105144             if (oldStore !== store && oldStore.autoDestroy) {
105145                 oldStore.destroyStore();
105146             } else {
105147                 oldStore.un({
105148                     scope: me,
105149                     load: me.onLoad,
105150                     exception: me.collapse
105151                 });
105152             }
105153             if (!store) {
105154                 me.store = null;
105155                 if (me.picker) {
105156                     me.picker.bindStore(null);
105157                 }
105158             }
105159         }
105160         if (store) {
105161             if (!initial) {
105162                 me.resetToDefault();
105163             }
105164
105165             me.store = Ext.data.StoreManager.lookup(store);
105166             me.store.on({
105167                 scope: me,
105168                 load: me.onLoad,
105169                 exception: me.collapse
105170             });
105171
105172             if (me.picker) {
105173                 me.picker.bindStore(store);
105174             }
105175         }
105176     },
105177
105178     onLoad: function() {
105179         var me = this,
105180             value = me.value;
105181
105182         // If performing a remote query upon the raw value...
105183         if (me.rawQuery) {
105184             me.rawQuery = false;
105185             me.syncSelection();
105186             if (me.picker && !me.picker.getSelectionModel().hasSelection()) {
105187                 me.doAutoSelect();
105188             }
105189         }
105190         // If store initial load or triggerAction: 'all' trigger click.
105191         else {
105192             // Set the value on load
105193             if (me.value) {
105194                 me.setValue(me.value);
105195             } else {
105196                 // There's no value.
105197                 // Highlight the first item in the list if autoSelect: true
105198                 if (me.store.getCount()) {
105199                     me.doAutoSelect();
105200                 } else {
105201                     me.setValue('');
105202                 }
105203             }
105204         }
105205     },
105206
105207     /**
105208      * @private
105209      * Execute the query with the raw contents within the textfield.
105210      */
105211     doRawQuery: function() {
105212         this.doQuery(this.getRawValue(), false, true);
105213     },
105214
105215     /**
105216      * Executes a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the query
105217      * allowing the query action to be canceled if needed.
105218      *
105219      * @param {String} queryString The SQL query to execute
105220      * @param {Boolean} [forceAll=false] `true` to force the query to execute even if there are currently fewer characters in
105221      * the field than the minimum specified by the `{@link #minChars}` config option. It also clears any filter
105222      * previously saved in the current store.
105223      * @param {Boolean} [rawQuery=false] Pass as true if the raw typed value is being used as the query string. This causes the
105224      * resulting store load to leave the raw value undisturbed.
105225      * @return {Boolean} true if the query was permitted to run, false if it was cancelled by a {@link #beforequery}
105226      * handler.
105227      */
105228     doQuery: function(queryString, forceAll, rawQuery) {
105229         queryString = queryString || '';
105230
105231         // store in object and pass by reference in 'beforequery'
105232         // so that client code can modify values.
105233         var me = this,
105234             qe = {
105235                 query: queryString,
105236                 forceAll: forceAll,
105237                 combo: me,
105238                 cancel: false
105239             },
105240             store = me.store,
105241             isLocalMode = me.queryMode === 'local';
105242
105243         if (me.fireEvent('beforequery', qe) === false || qe.cancel) {
105244             return false;
105245         }
105246
105247         // get back out possibly modified values
105248         queryString = qe.query;
105249         forceAll = qe.forceAll;
105250
105251         // query permitted to run
105252         if (forceAll || (queryString.length >= me.minChars)) {
105253             // expand before starting query so LoadMask can position itself correctly
105254             me.expand();
105255
105256             // make sure they aren't querying the same thing
105257             if (!me.queryCaching || me.lastQuery !== queryString) {
105258                 me.lastQuery = queryString;
105259
105260                 if (isLocalMode) {
105261                     // forceAll means no filtering - show whole dataset.
105262                     if (forceAll) {
105263                         store.clearFilter();
105264                     } else {
105265                         // Clear filter, but supress event so that the BoundList is not immediately updated.
105266                         store.clearFilter(true);
105267                         store.filter(me.displayField, queryString);
105268                     }
105269                 } else {
105270                     // Set flag for onLoad handling to know how the Store was loaded
105271                     me.rawQuery = rawQuery;
105272
105273                     // In queryMode: 'remote', we assume Store filters are added by the developer as remote filters,
105274                     // and these are automatically passed as params with every load call, so we do *not* call clearFilter.
105275                     if (me.pageSize) {
105276                         // if we're paging, we've changed the query so start at page 1.
105277                         me.loadPage(1);
105278                     } else {
105279                         store.load({
105280                             params: me.getParams(queryString)
105281                         });
105282                     }
105283                 }
105284             }
105285
105286             // Clear current selection if it does not match the current value in the field
105287             if (me.getRawValue() !== me.getDisplayValue()) {
105288                 me.ignoreSelection++;
105289                 me.picker.getSelectionModel().deselectAll();
105290                 me.ignoreSelection--;
105291             }
105292
105293             if (isLocalMode) {
105294                 me.doAutoSelect();
105295             }
105296             if (me.typeAhead) {
105297                 me.doTypeAhead();
105298             }
105299         }
105300         return true;
105301     },
105302
105303     loadPage: function(pageNum){
105304         this.store.loadPage(pageNum, {
105305             params: this.getParams(this.lastQuery)
105306         });
105307     },
105308
105309     onPageChange: function(toolbar, newPage){
105310         /*
105311          * Return false here so we can call load ourselves and inject the query param.
105312          * We don't want to do this for every store load since the developer may load
105313          * the store through some other means so we won't add the query param.
105314          */
105315         this.loadPage(newPage);
105316         return false;
105317     },
105318
105319     // private
105320     getParams: function(queryString) {
105321         var params = {},
105322             param = this.queryParam;
105323
105324         if (param) {
105325             params[param] = queryString;
105326         }
105327         return params;
105328     },
105329
105330     /**
105331      * @private
105332      * If the autoSelect config is true, and the picker is open, highlights the first item.
105333      */
105334     doAutoSelect: function() {
105335         var me = this,
105336             picker = me.picker,
105337             lastSelected, itemNode;
105338         if (picker && me.autoSelect && me.store.getCount() > 0) {
105339             // Highlight the last selected item and scroll it into view
105340             lastSelected = picker.getSelectionModel().lastSelected;
105341             itemNode = picker.getNode(lastSelected || 0);
105342             if (itemNode) {
105343                 picker.highlightItem(itemNode);
105344                 picker.listEl.scrollChildIntoView(itemNode, false);
105345             }
105346         }
105347     },
105348
105349     doTypeAhead: function() {
105350         if (!this.typeAheadTask) {
105351             this.typeAheadTask = Ext.create('Ext.util.DelayedTask', this.onTypeAhead, this);
105352         }
105353         if (this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE) {
105354             this.typeAheadTask.delay(this.typeAheadDelay);
105355         }
105356     },
105357
105358     onTriggerClick: function() {
105359         var me = this;
105360         if (!me.readOnly && !me.disabled) {
105361             if (me.isExpanded) {
105362                 me.collapse();
105363             } else {
105364                 me.onFocus({});
105365                 if (me.triggerAction === 'all') {
105366                     me.doQuery(me.allQuery, true);
105367                 } else {
105368                     me.doQuery(me.getRawValue(), false, true);
105369                 }
105370             }
105371             me.inputEl.focus();
105372         }
105373     },
105374
105375
105376     // store the last key and doQuery if relevant
105377     onKeyUp: function(e, t) {
105378         var me = this,
105379             key = e.getKey();
105380
105381         if (!me.readOnly && !me.disabled && me.editable) {
105382             me.lastKey = key;
105383             // we put this in a task so that we can cancel it if a user is
105384             // in and out before the queryDelay elapses
105385
105386             // perform query w/ any normal key or backspace or delete
105387             if (!e.isSpecialKey() || key == e.BACKSPACE || key == e.DELETE) {
105388                 me.doQueryTask.delay(me.queryDelay);
105389             }
105390         }
105391
105392         if (me.enableKeyEvents) {
105393             me.callParent(arguments);
105394         }
105395     },
105396
105397     initEvents: function() {
105398         var me = this;
105399         me.callParent();
105400
105401         /*
105402          * Setup keyboard handling. If enableKeyEvents is true, we already have
105403          * a listener on the inputEl for keyup, so don't create a second.
105404          */
105405         if (!me.enableKeyEvents) {
105406             me.mon(me.inputEl, 'keyup', me.onKeyUp, me);
105407         }
105408     },
105409     
105410     onDestroy: function(){
105411         this.bindStore(null);
105412         this.callParent();    
105413     },
105414
105415     createPicker: function() {
105416         var me = this,
105417             picker,
105418             menuCls = Ext.baseCSSPrefix + 'menu',
105419             opts = Ext.apply({
105420                 pickerField: me,
105421                 selModel: {
105422                     mode: me.multiSelect ? 'SIMPLE' : 'SINGLE'
105423                 },
105424                 floating: true,
105425                 hidden: true,
105426                 ownerCt: me.ownerCt,
105427                 cls: me.el.up('.' + menuCls) ? menuCls : '',
105428                 store: me.store,
105429                 displayField: me.displayField,
105430                 focusOnToFront: false,
105431                 pageSize: me.pageSize,
105432                 tpl: me.tpl
105433             }, me.listConfig, me.defaultListConfig);
105434
105435         picker = me.picker = Ext.create('Ext.view.BoundList', opts);
105436         if (me.pageSize) {
105437             picker.pagingToolbar.on('beforechange', me.onPageChange, me);
105438         }
105439
105440         me.mon(picker, {
105441             itemclick: me.onItemClick,
105442             refresh: me.onListRefresh,
105443             scope: me
105444         });
105445
105446         me.mon(picker.getSelectionModel(), {
105447             'beforeselect': me.onBeforeSelect,
105448             'beforedeselect': me.onBeforeDeselect,
105449             'selectionchange': me.onListSelectionChange,
105450             scope: me
105451         });
105452
105453         return picker;
105454     },
105455
105456     alignPicker: function(){
105457         var me = this,
105458             picker = me.picker,
105459             heightAbove = me.getPosition()[1] - Ext.getBody().getScroll().top,
105460             heightBelow = Ext.Element.getViewHeight() - heightAbove - me.getHeight(),
105461             space = Math.max(heightAbove, heightBelow);
105462
105463         me.callParent();
105464         if (picker.getHeight() > space) {
105465             picker.setHeight(space - 5); // have some leeway so we aren't flush against
105466             me.doAlign();
105467         }
105468     },
105469
105470     onListRefresh: function() {
105471         this.alignPicker();
105472         this.syncSelection();
105473     },
105474
105475     onItemClick: function(picker, record){
105476         /*
105477          * If we're doing single selection, the selection change events won't fire when
105478          * clicking on the selected element. Detect it here.
105479          */
105480         var me = this,
105481             lastSelection = me.lastSelection,
105482             valueField = me.valueField,
105483             selected;
105484
105485         if (!me.multiSelect && lastSelection) {
105486             selected = lastSelection[0];
105487             if (selected && (record.get(valueField) === selected.get(valueField))) {
105488                 // Make sure we also update the display value if it's only partial
105489                 me.displayTplData = [record.data];
105490                 me.setRawValue(me.getDisplayValue());
105491                 me.collapse();
105492             }
105493         }
105494     },
105495
105496     onBeforeSelect: function(list, record) {
105497         return this.fireEvent('beforeselect', this, record, record.index);
105498     },
105499
105500     onBeforeDeselect: function(list, record) {
105501         return this.fireEvent('beforedeselect', this, record, record.index);
105502     },
105503
105504     onListSelectionChange: function(list, selectedRecords) {
105505         var me = this,
105506             isMulti = me.multiSelect,
105507             hasRecords = selectedRecords.length > 0;
105508         // Only react to selection if it is not called from setValue, and if our list is
105509         // expanded (ignores changes to the selection model triggered elsewhere)
105510         if (!me.ignoreSelection && me.isExpanded) {
105511             if (!isMulti) {
105512                 Ext.defer(me.collapse, 1, me);
105513             }
105514             /*
105515              * Only set the value here if we're in multi selection mode or we have
105516              * a selection. Otherwise setValue will be called with an empty value
105517              * which will cause the change event to fire twice.
105518              */
105519             if (isMulti || hasRecords) {
105520                 me.setValue(selectedRecords, false);
105521             }
105522             if (hasRecords) {
105523                 me.fireEvent('select', me, selectedRecords);
105524             }
105525             me.inputEl.focus();
105526         }
105527     },
105528
105529     /**
105530      * @private
105531      * Enables the key nav for the BoundList when it is expanded.
105532      */
105533     onExpand: function() {
105534         var me = this,
105535             keyNav = me.listKeyNav,
105536             selectOnTab = me.selectOnTab,
105537             picker = me.getPicker();
105538
105539         // Handle BoundList navigation from the input field. Insert a tab listener specially to enable selectOnTab.
105540         if (keyNav) {
105541             keyNav.enable();
105542         } else {
105543             keyNav = me.listKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
105544                 boundList: picker,
105545                 forceKeyDown: true,
105546                 tab: function(e) {
105547                     if (selectOnTab) {
105548                         this.selectHighlighted(e);
105549                         me.triggerBlur();
105550                     }
105551                     // Tab key event is allowed to propagate to field
105552                     return true;
105553                 }
105554             });
105555         }
105556
105557         // While list is expanded, stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
105558         if (selectOnTab) {
105559             me.ignoreMonitorTab = true;
105560         }
105561
105562         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
105563         me.inputEl.focus();
105564     },
105565
105566     /**
105567      * @private
105568      * Disables the key nav for the BoundList when it is collapsed.
105569      */
105570     onCollapse: function() {
105571         var me = this,
105572             keyNav = me.listKeyNav;
105573         if (keyNav) {
105574             keyNav.disable();
105575             me.ignoreMonitorTab = false;
105576         }
105577     },
105578
105579     /**
105580      * Selects an item by a {@link Ext.data.Model Model}, or by a key value.
105581      * @param {Object} r
105582      */
105583     select: function(r) {
105584         this.setValue(r, true);
105585     },
105586
105587     /**
105588      * Finds the record by searching for a specific field/value combination.
105589      * @param {String} field The name of the field to test.
105590      * @param {Object} value The value to match the field against.
105591      * @return {Ext.data.Model} The matched record or false.
105592      */
105593     findRecord: function(field, value) {
105594         var ds = this.store,
105595             idx = ds.findExact(field, value);
105596         return idx !== -1 ? ds.getAt(idx) : false;
105597     },
105598
105599     /**
105600      * Finds the record by searching values in the {@link #valueField}.
105601      * @param {Object} value The value to match the field against.
105602      * @return {Ext.data.Model} The matched record or false.
105603      */
105604     findRecordByValue: function(value) {
105605         return this.findRecord(this.valueField, value);
105606     },
105607
105608     /**
105609      * Finds the record by searching values in the {@link #displayField}.
105610      * @param {Object} value The value to match the field against.
105611      * @return {Ext.data.Model} The matched record or false.
105612      */
105613     findRecordByDisplay: function(value) {
105614         return this.findRecord(this.displayField, value);
105615     },
105616
105617     /**
105618      * Sets the specified value(s) into the field. For each value, if a record is found in the {@link #store} that
105619      * matches based on the {@link #valueField}, then that record's {@link #displayField} will be displayed in the
105620      * field. If no match is found, and the {@link #valueNotFoundText} config option is defined, then that will be
105621      * displayed as the default field text. Otherwise a blank value will be shown, although the value will still be set.
105622      * @param {String/String[]} value The value(s) to be set. Can be either a single String or {@link Ext.data.Model},
105623      * or an Array of Strings or Models.
105624      * @return {Ext.form.field.Field} this
105625      */
105626     setValue: function(value, doSelect) {
105627         var me = this,
105628             valueNotFoundText = me.valueNotFoundText,
105629             inputEl = me.inputEl,
105630             i, len, record,
105631             models = [],
105632             displayTplData = [],
105633             processedValue = [];
105634
105635         if (me.store.loading) {
105636             // Called while the Store is loading. Ensure it is processed by the onLoad method.
105637             me.value = value;
105638             me.setHiddenValue(me.value);
105639             return me;
105640         }
105641
105642         // This method processes multi-values, so ensure value is an array.
105643         value = Ext.Array.from(value);
105644
105645         // Loop through values
105646         for (i = 0, len = value.length; i < len; i++) {
105647             record = value[i];
105648             if (!record || !record.isModel) {
105649                 record = me.findRecordByValue(record);
105650             }
105651             // record found, select it.
105652             if (record) {
105653                 models.push(record);
105654                 displayTplData.push(record.data);
105655                 processedValue.push(record.get(me.valueField));
105656             }
105657             // record was not found, this could happen because
105658             // store is not loaded or they set a value not in the store
105659             else {
105660                 // If we are allowing insertion of values not represented in the Store, then set the value, and the display value
105661                 if (!me.forceSelection) {
105662                     displayTplData.push(value[i]);
105663                     processedValue.push(value[i]);
105664                 }
105665                 // Else, if valueNotFoundText is defined, display it, otherwise display nothing for this value
105666                 else if (Ext.isDefined(valueNotFoundText)) {
105667                     displayTplData.push(valueNotFoundText);
105668                 }
105669             }
105670         }
105671
105672         // Set the value of this field. If we are multiselecting, then that is an array.
105673         me.setHiddenValue(processedValue);
105674         me.value = me.multiSelect ? processedValue : processedValue[0];
105675         if (!Ext.isDefined(me.value)) {
105676             me.value = null;
105677         }
105678         me.displayTplData = displayTplData; //store for getDisplayValue method
105679         me.lastSelection = me.valueModels = models;
105680
105681         if (inputEl && me.emptyText && !Ext.isEmpty(value)) {
105682             inputEl.removeCls(me.emptyCls);
105683         }
105684
105685         // Calculate raw value from the collection of Model data
105686         me.setRawValue(me.getDisplayValue());
105687         me.checkChange();
105688
105689         if (doSelect !== false) {
105690             me.syncSelection();
105691         }
105692         me.applyEmptyText();
105693
105694         return me;
105695     },
105696
105697     /**
105698      * @private
105699      * Set the value of {@link #hiddenDataEl}
105700      * Dynamically adds and removes input[type=hidden] elements
105701      */
105702     setHiddenValue: function(values){
105703         var me = this, i;
105704         if (!me.hiddenDataEl) {
105705             return;
105706         }
105707         values = Ext.Array.from(values);
105708         var dom = me.hiddenDataEl.dom,
105709             childNodes = dom.childNodes,
105710             input = childNodes[0],
105711             valueCount = values.length,
105712             childrenCount = childNodes.length;
105713         
105714         if (!input && valueCount > 0) {
105715             me.hiddenDataEl.update(Ext.DomHelper.markup({tag:'input', type:'hidden', name:me.name}));
105716             childrenCount = 1;
105717             input = dom.firstChild;
105718         }
105719         while (childrenCount > valueCount) {
105720             dom.removeChild(childNodes[0]);
105721             -- childrenCount;
105722         }
105723         while (childrenCount < valueCount) {
105724             dom.appendChild(input.cloneNode(true));
105725             ++ childrenCount;
105726         }
105727         for (i = 0; i < valueCount; i++) {
105728             childNodes[i].value = values[i];
105729         }
105730     },
105731
105732     /**
105733      * @private Generates the string value to be displayed in the text field for the currently stored value
105734      */
105735     getDisplayValue: function() {
105736         return this.displayTpl.apply(this.displayTplData);
105737     },
105738
105739     getValue: function() {
105740         // If the user has not changed the raw field value since a value was selected from the list,
105741         // then return the structured value from the selection. If the raw field value is different
105742         // than what would be displayed due to selection, return that raw value.
105743         var me = this,
105744             picker = me.picker,
105745             rawValue = me.getRawValue(), //current value of text field
105746             value = me.value; //stored value from last selection or setValue() call
105747
105748         if (me.getDisplayValue() !== rawValue) {
105749             value = rawValue;
105750             me.value = me.displayTplData = me.valueModels = null;
105751             if (picker) {
105752                 me.ignoreSelection++;
105753                 picker.getSelectionModel().deselectAll();
105754                 me.ignoreSelection--;
105755             }
105756         }
105757
105758         return value;
105759     },
105760
105761     getSubmitValue: function() {
105762         return this.getValue();
105763     },
105764
105765     isEqual: function(v1, v2) {
105766         var fromArray = Ext.Array.from,
105767             i, len;
105768
105769         v1 = fromArray(v1);
105770         v2 = fromArray(v2);
105771         len = v1.length;
105772
105773         if (len !== v2.length) {
105774             return false;
105775         }
105776
105777         for(i = 0; i < len; i++) {
105778             if (v2[i] !== v1[i]) {
105779                 return false;
105780             }
105781         }
105782
105783         return true;
105784     },
105785
105786     /**
105787      * Clears any value currently set in the ComboBox.
105788      */
105789     clearValue: function() {
105790         this.setValue([]);
105791     },
105792
105793     /**
105794      * @private Synchronizes the selection in the picker to match the current value of the combobox.
105795      */
105796     syncSelection: function() {
105797         var me = this,
105798             ExtArray = Ext.Array,
105799             picker = me.picker,
105800             selection, selModel;
105801         if (picker) {
105802             // From the value, find the Models that are in the store's current data
105803             selection = [];
105804             ExtArray.forEach(me.valueModels || [], function(value) {
105805                 if (value && value.isModel && me.store.indexOf(value) >= 0) {
105806                     selection.push(value);
105807                 }
105808             });
105809
105810             // Update the selection to match
105811             me.ignoreSelection++;
105812             selModel = picker.getSelectionModel();
105813             selModel.deselectAll();
105814             if (selection.length) {
105815                 selModel.select(selection);
105816             }
105817             me.ignoreSelection--;
105818         }
105819     }
105820 });
105821
105822 /**
105823  * A month picker component. This class is used by the {@link Ext.picker.Date Date picker} class
105824  * to allow browsing and selection of year/months combinations.
105825  */
105826 Ext.define('Ext.picker.Month', {
105827     extend: 'Ext.Component',
105828     requires: ['Ext.XTemplate', 'Ext.util.ClickRepeater', 'Ext.Date', 'Ext.button.Button'],
105829     alias: 'widget.monthpicker',
105830     alternateClassName: 'Ext.MonthPicker',
105831
105832     renderTpl: [
105833         '<div id="{id}-bodyEl" class="{baseCls}-body">',
105834           '<div class="{baseCls}-months">',
105835               '<tpl for="months">',
105836                   '<div class="{parent.baseCls}-item {parent.baseCls}-month"><a href="#" hidefocus="on">{.}</a></div>',
105837               '</tpl>',
105838           '</div>',
105839           '<div class="{baseCls}-years">',
105840               '<div class="{baseCls}-yearnav">',
105841                   '<button id="{id}-prevEl" class="{baseCls}-yearnav-prev"></button>',
105842                   '<button id="{id}-nextEl" class="{baseCls}-yearnav-next"></button>',
105843               '</div>',
105844               '<tpl for="years">',
105845                   '<div class="{parent.baseCls}-item {parent.baseCls}-year"><a href="#" hidefocus="on">{.}</a></div>',
105846               '</tpl>',
105847           '</div>',
105848           '<div class="' + Ext.baseCSSPrefix + 'clear"></div>',
105849         '</div>',
105850         '<tpl if="showButtons">',
105851           '<div id="{id}-buttonsEl" class="{baseCls}-buttons"></div>',
105852         '</tpl>'
105853     ],
105854
105855     /**
105856      * @cfg {String} okText The text to display on the ok button.
105857      */
105858     okText: 'OK',
105859
105860     /**
105861      * @cfg {String} cancelText The text to display on the cancel button.
105862      */
105863     cancelText: 'Cancel',
105864
105865     /**
105866      * @cfg {String} baseCls The base CSS class to apply to the picker element. Defaults to <tt>'x-monthpicker'</tt>
105867      */
105868     baseCls: Ext.baseCSSPrefix + 'monthpicker',
105869
105870     /**
105871      * @cfg {Boolean} showButtons True to show ok and cancel buttons below the picker.
105872      */
105873     showButtons: true,
105874
105875     /**
105876      * @cfg {String} selectedCls The class to be added to selected items in the picker. Defaults to
105877      * <tt>'x-monthpicker-selected'</tt>
105878      */
105879
105880     /**
105881      * @cfg {Date/Number[]} value The default value to set. See {@link #setValue}
105882      */
105883     width: 178,
105884
105885     // used when attached to date picker which isnt showing buttons
105886     smallCls: Ext.baseCSSPrefix + 'monthpicker-small',
105887
105888     // private
105889     totalYears: 10,
105890     yearOffset: 5, // 10 years in total, 2 per row
105891     monthOffset: 6, // 12 months, 2 per row
105892
105893     // private, inherit docs
105894     initComponent: function(){
105895         var me = this;
105896
105897         me.selectedCls = me.baseCls + '-selected';
105898         me.addEvents(
105899             /**
105900              * @event cancelclick
105901              * Fires when the cancel button is pressed.
105902              * @param {Ext.picker.Month} this
105903              */
105904             'cancelclick',
105905
105906             /**
105907              * @event monthclick
105908              * Fires when a month is clicked.
105909              * @param {Ext.picker.Month} this
105910              * @param {Array} value The current value
105911              */
105912             'monthclick',
105913
105914             /**
105915              * @event monthdblclick
105916              * Fires when a month is clicked.
105917              * @param {Ext.picker.Month} this
105918              * @param {Array} value The current value
105919              */
105920             'monthdblclick',
105921
105922             /**
105923              * @event okclick
105924              * Fires when the ok button is pressed.
105925              * @param {Ext.picker.Month} this
105926              * @param {Array} value The current value
105927              */
105928             'okclick',
105929
105930             /**
105931              * @event select
105932              * Fires when a month/year is selected.
105933              * @param {Ext.picker.Month} this
105934              * @param {Array} value The current value
105935              */
105936             'select',
105937
105938             /**
105939              * @event yearclick
105940              * Fires when a year is clicked.
105941              * @param {Ext.picker.Month} this
105942              * @param {Array} value The current value
105943              */
105944             'yearclick',
105945
105946             /**
105947              * @event yeardblclick
105948              * Fires when a year is clicked.
105949              * @param {Ext.picker.Month} this
105950              * @param {Array} value The current value
105951              */
105952             'yeardblclick'
105953         );
105954         if (me.small) {
105955             me.addCls(me.smallCls);
105956         }
105957         me.setValue(me.value);
105958         me.activeYear = me.getYear(new Date().getFullYear() - 4, -4);
105959         this.callParent();
105960     },
105961
105962     // private, inherit docs
105963     onRender: function(ct, position){
105964         var me = this,
105965             i = 0,
105966             months = [],
105967             shortName = Ext.Date.getShortMonthName,
105968             monthLen = me.monthOffset;
105969
105970         for (; i < monthLen; ++i) {
105971             months.push(shortName(i), shortName(i + monthLen));
105972         }
105973
105974         Ext.apply(me.renderData, {
105975             months: months,
105976             years: me.getYears(),
105977             showButtons: me.showButtons
105978         });
105979
105980         me.addChildEls('bodyEl', 'prevEl', 'nextEl', 'buttonsEl');
105981
105982         me.callParent(arguments);
105983     },
105984
105985     // private, inherit docs
105986     afterRender: function(){
105987         var me = this,
105988             body = me.bodyEl,
105989             buttonsEl = me.buttonsEl;
105990
105991         me.callParent();
105992
105993         me.mon(body, 'click', me.onBodyClick, me);
105994         me.mon(body, 'dblclick', me.onBodyClick, me);
105995
105996         // keep a reference to the year/month elements since we'll be re-using them
105997         me.years = body.select('.' + me.baseCls + '-year a');
105998         me.months = body.select('.' + me.baseCls + '-month a');
105999
106000         if (me.showButtons) {
106001             me.okBtn = Ext.create('Ext.button.Button', {
106002                 text: me.okText,
106003                 renderTo: buttonsEl,
106004                 handler: me.onOkClick,
106005                 scope: me
106006             });
106007             me.cancelBtn = Ext.create('Ext.button.Button', {
106008                 text: me.cancelText,
106009                 renderTo: buttonsEl,
106010                 handler: me.onCancelClick,
106011                 scope: me
106012             });
106013         }
106014
106015         me.backRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
106016             handler: Ext.Function.bind(me.adjustYear, me, [-me.totalYears])
106017         });
106018
106019         me.prevEl.addClsOnOver(me.baseCls + '-yearnav-prev-over');
106020         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
106021             handler: Ext.Function.bind(me.adjustYear, me, [me.totalYears])
106022         });
106023         me.nextEl.addClsOnOver(me.baseCls + '-yearnav-next-over');
106024         me.updateBody();
106025     },
106026
106027     /**
106028      * Set the value for the picker.
106029      * @param {Date/Number[]} value The value to set. It can be a Date object, where the month/year will be extracted, or
106030      * it can be an array, with the month as the first index and the year as the second.
106031      * @return {Ext.picker.Month} this
106032      */
106033     setValue: function(value){
106034         var me = this,
106035             active = me.activeYear,
106036             offset = me.monthOffset,
106037             year,
106038             index;
106039
106040         if (!value) {
106041             me.value = [null, null];
106042         } else if (Ext.isDate(value)) {
106043             me.value = [value.getMonth(), value.getFullYear()];
106044         } else {
106045             me.value = [value[0], value[1]];
106046         }
106047
106048         if (me.rendered) {
106049             year = me.value[1];
106050             if (year !== null) {
106051                 if ((year < active || year > active + me.yearOffset)) {
106052                     me.activeYear = year - me.yearOffset + 1;
106053                 }
106054             }
106055             me.updateBody();
106056         }
106057
106058         return me;
106059     },
106060
106061     /**
106062      * Gets the selected value. It is returned as an array [month, year]. It may
106063      * be a partial value, for example [null, 2010]. The month is returned as
106064      * 0 based.
106065      * @return {Number[]} The selected value
106066      */
106067     getValue: function(){
106068         return this.value;
106069     },
106070
106071     /**
106072      * Checks whether the picker has a selection
106073      * @return {Boolean} Returns true if both a month and year have been selected
106074      */
106075     hasSelection: function(){
106076         var value = this.value;
106077         return value[0] !== null && value[1] !== null;
106078     },
106079
106080     /**
106081      * Get an array of years to be pushed in the template. It is not in strict
106082      * numerical order because we want to show them in columns.
106083      * @private
106084      * @return {Number[]} An array of years
106085      */
106086     getYears: function(){
106087         var me = this,
106088             offset = me.yearOffset,
106089             start = me.activeYear, // put the "active" year on the left
106090             end = start + offset,
106091             i = start,
106092             years = [];
106093
106094         for (; i < end; ++i) {
106095             years.push(i, i + offset);
106096         }
106097
106098         return years;
106099     },
106100
106101     /**
106102      * Update the years in the body based on any change
106103      * @private
106104      */
106105     updateBody: function(){
106106         var me = this,
106107             years = me.years,
106108             months = me.months,
106109             yearNumbers = me.getYears(),
106110             cls = me.selectedCls,
106111             value = me.getYear(null),
106112             month = me.value[0],
106113             monthOffset = me.monthOffset,
106114             year;
106115
106116         if (me.rendered) {
106117             years.removeCls(cls);
106118             months.removeCls(cls);
106119             years.each(function(el, all, index){
106120                 year = yearNumbers[index];
106121                 el.dom.innerHTML = year;
106122                 if (year == value) {
106123                     el.dom.className = cls;
106124                 }
106125             });
106126             if (month !== null) {
106127                 if (month < monthOffset) {
106128                     month = month * 2;
106129                 } else {
106130                     month = (month - monthOffset) * 2 + 1;
106131                 }
106132                 months.item(month).addCls(cls);
106133             }
106134         }
106135     },
106136
106137     /**
106138      * Gets the current year value, or the default.
106139      * @private
106140      * @param {Number} defaultValue The default value to use if the year is not defined.
106141      * @param {Number} offset A number to offset the value by
106142      * @return {Number} The year value
106143      */
106144     getYear: function(defaultValue, offset) {
106145         var year = this.value[1];
106146         offset = offset || 0;
106147         return year === null ? defaultValue : year + offset;
106148     },
106149
106150     /**
106151      * React to clicks on the body
106152      * @private
106153      */
106154     onBodyClick: function(e, t) {
106155         var me = this,
106156             isDouble = e.type == 'dblclick';
106157
106158         if (e.getTarget('.' + me.baseCls + '-month')) {
106159             e.stopEvent();
106160             me.onMonthClick(t, isDouble);
106161         } else if (e.getTarget('.' + me.baseCls + '-year')) {
106162             e.stopEvent();
106163             me.onYearClick(t, isDouble);
106164         }
106165     },
106166
106167     /**
106168      * Modify the year display by passing an offset.
106169      * @param {Number} [offset=10] The offset to move by.
106170      */
106171     adjustYear: function(offset){
106172         if (typeof offset != 'number') {
106173             offset = this.totalYears;
106174         }
106175         this.activeYear += offset;
106176         this.updateBody();
106177     },
106178
106179     /**
106180      * React to the ok button being pressed
106181      * @private
106182      */
106183     onOkClick: function(){
106184         this.fireEvent('okclick', this, this.value);
106185     },
106186
106187     /**
106188      * React to the cancel button being pressed
106189      * @private
106190      */
106191     onCancelClick: function(){
106192         this.fireEvent('cancelclick', this);
106193     },
106194
106195     /**
106196      * React to a month being clicked
106197      * @private
106198      * @param {HTMLElement} target The element that was clicked
106199      * @param {Boolean} isDouble True if the event was a doubleclick
106200      */
106201     onMonthClick: function(target, isDouble){
106202         var me = this;
106203         me.value[0] = me.resolveOffset(me.months.indexOf(target), me.monthOffset);
106204         me.updateBody();
106205         me.fireEvent('month' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
106206         me.fireEvent('select', me, me.value);
106207     },
106208
106209     /**
106210      * React to a year being clicked
106211      * @private
106212      * @param {HTMLElement} target The element that was clicked
106213      * @param {Boolean} isDouble True if the event was a doubleclick
106214      */
106215     onYearClick: function(target, isDouble){
106216         var me = this;
106217         me.value[1] = me.activeYear + me.resolveOffset(me.years.indexOf(target), me.yearOffset);
106218         me.updateBody();
106219         me.fireEvent('year' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
106220         me.fireEvent('select', me, me.value);
106221
106222     },
106223
106224     /**
106225      * Returns an offsetted number based on the position in the collection. Since our collections aren't
106226      * numerically ordered, this function helps to normalize those differences.
106227      * @private
106228      * @param {Object} index
106229      * @param {Object} offset
106230      * @return {Number} The correctly offsetted number
106231      */
106232     resolveOffset: function(index, offset){
106233         if (index % 2 === 0) {
106234             return (index / 2);
106235         } else {
106236             return offset + Math.floor(index / 2);
106237         }
106238     },
106239
106240     // private, inherit docs
106241     beforeDestroy: function(){
106242         var me = this;
106243         me.years = me.months = null;
106244         Ext.destroyMembers(me, 'backRepeater', 'nextRepeater', 'okBtn', 'cancelBtn');
106245         me.callParent();
106246     }
106247 });
106248
106249 /**
106250  * A date picker. This class is used by the Ext.form.field.Date field to allow browsing and selection of valid
106251  * dates in a popup next to the field, but may also be used with other components.
106252  *
106253  * Typically you will need to implement a handler function to be notified when the user chooses a date from the picker;
106254  * you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method.
106255  *
106256  * By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate},
106257  * {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs.
106258  *
106259  * All the string values documented below may be overridden by including an Ext locale file in your page.
106260  *
106261  *     @example
106262  *     Ext.create('Ext.panel.Panel', {
106263  *         title: 'Choose a future date:',
106264  *         width: 200,
106265  *         bodyPadding: 10,
106266  *         renderTo: Ext.getBody(),
106267  *         items: [{
106268  *             xtype: 'datepicker',
106269  *             minDate: new Date(),
106270  *             handler: function(picker, date) {
106271  *                 // do something with the selected date
106272  *             }
106273  *         }]
106274  *     });
106275  */
106276 Ext.define('Ext.picker.Date', {
106277     extend: 'Ext.Component',
106278     requires: [
106279         'Ext.XTemplate',
106280         'Ext.button.Button',
106281         'Ext.button.Split',
106282         'Ext.util.ClickRepeater',
106283         'Ext.util.KeyNav',
106284         'Ext.EventObject',
106285         'Ext.fx.Manager',
106286         'Ext.picker.Month'
106287     ],
106288     alias: 'widget.datepicker',
106289     alternateClassName: 'Ext.DatePicker',
106290
106291     renderTpl: [
106292         '<div class="{cls}" id="{id}" role="grid" title="{ariaTitle} {value:this.longDay}">',
106293             '<div role="presentation" class="{baseCls}-header">',
106294                 '<div class="{baseCls}-prev"><a id="{id}-prevEl" href="#" role="button" title="{prevText}"></a></div>',
106295                 '<div class="{baseCls}-month" id="{id}-middleBtnEl"></div>',
106296                 '<div class="{baseCls}-next"><a id="{id}-nextEl" href="#" role="button" title="{nextText}"></a></div>',
106297             '</div>',
106298             '<table id="{id}-eventEl" class="{baseCls}-inner" cellspacing="0" role="presentation">',
106299                 '<thead role="presentation"><tr role="presentation">',
106300                     '<tpl for="dayNames">',
106301                         '<th role="columnheader" title="{.}"><span>{.:this.firstInitial}</span></th>',
106302                     '</tpl>',
106303                 '</tr></thead>',
106304                 '<tbody role="presentation"><tr role="presentation">',
106305                     '<tpl for="days">',
106306                         '{#:this.isEndOfWeek}',
106307                         '<td role="gridcell" id="{[Ext.id()]}">',
106308                             '<a role="presentation" href="#" hidefocus="on" class="{parent.baseCls}-date" tabIndex="1">',
106309                                 '<em role="presentation"><span role="presentation"></span></em>',
106310                             '</a>',
106311                         '</td>',
106312                     '</tpl>',
106313                 '</tr></tbody>',
106314             '</table>',
106315             '<tpl if="showToday">',
106316                 '<div id="{id}-footerEl" role="presentation" class="{baseCls}-footer"></div>',
106317             '</tpl>',
106318         '</div>',
106319         {
106320             firstInitial: function(value) {
106321                 return value.substr(0,1);
106322             },
106323             isEndOfWeek: function(value) {
106324                 // convert from 1 based index to 0 based
106325                 // by decrementing value once.
106326                 value--;
106327                 var end = value % 7 === 0 && value !== 0;
106328                 return end ? '</tr><tr role="row">' : '';
106329             },
106330             longDay: function(value){
106331                 return Ext.Date.format(value, this.longDayFormat);
106332             }
106333         }
106334     ],
106335
106336     ariaTitle: 'Date Picker',
106337
106338     /**
106339      * @cfg {String} todayText
106340      * The text to display on the button that selects the current date
106341      */
106342     todayText : 'Today',
106343
106344     /**
106345      * @cfg {Function} handler
106346      * Optional. A function that will handle the select event of this picker. The handler is passed the following
106347      * parameters:
106348      *
106349      *   - `picker` : Ext.picker.Date
106350      *
106351      * This Date picker.
106352      *
106353      *   - `date` : Date
106354      *
106355      * The selected date.
106356      */
106357
106358     /**
106359      * @cfg {Object} scope
106360      * The scope (`this` reference) in which the `{@link #handler}` function will be called. Defaults to this
106361      * DatePicker instance.
106362      */
106363
106364     /**
106365      * @cfg {String} todayTip
106366      * A string used to format the message for displaying in a tooltip over the button that selects the current date.
106367      * The `{0}` token in string is replaced by today's date.
106368      */
106369     todayTip : '{0} (Spacebar)',
106370
106371     /**
106372      * @cfg {String} minText
106373      * The error text to display if the minDate validation fails.
106374      */
106375     minText : 'This date is before the minimum date',
106376
106377     /**
106378      * @cfg {String} maxText
106379      * The error text to display if the maxDate validation fails.
106380      */
106381     maxText : 'This date is after the maximum date',
106382
106383     /**
106384      * @cfg {String} format
106385      * The default date format string which can be overriden for localization support. The format must be valid
106386      * according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}).
106387      */
106388
106389     /**
106390      * @cfg {String} disabledDaysText
106391      * The tooltip to display when the date falls on a disabled day.
106392      */
106393     disabledDaysText : 'Disabled',
106394
106395     /**
106396      * @cfg {String} disabledDatesText
106397      * The tooltip text to display when the date falls on a disabled date.
106398      */
106399     disabledDatesText : 'Disabled',
106400
106401     /**
106402      * @cfg {String[]} monthNames
106403      * An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames)
106404      */
106405
106406     /**
106407      * @cfg {String[]} dayNames
106408      * An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames)
106409      */
106410
106411     /**
106412      * @cfg {String} nextText
106413      * The next month navigation button tooltip
106414      */
106415     nextText : 'Next Month (Control+Right)',
106416
106417     /**
106418      * @cfg {String} prevText
106419      * The previous month navigation button tooltip
106420      */
106421     prevText : 'Previous Month (Control+Left)',
106422
106423     /**
106424      * @cfg {String} monthYearText
106425      * The header month selector tooltip
106426      */
106427     monthYearText : 'Choose a month (Control+Up/Down to move years)',
106428
106429     /**
106430      * @cfg {Number} startDay
106431      * Day index at which the week should begin, 0-based (defaults to Sunday)
106432      */
106433     startDay : 0,
106434
106435     /**
106436      * @cfg {Boolean} showToday
106437      * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar that
106438      * selects the current date.
106439      */
106440     showToday : true,
106441
106442     /**
106443      * @cfg {Date} [minDate=null]
106444      * Minimum allowable date (JavaScript date object)
106445      */
106446
106447     /**
106448      * @cfg {Date} [maxDate=null]
106449      * Maximum allowable date (JavaScript date object)
106450      */
106451
106452     /**
106453      * @cfg {Number[]} [disabledDays=null]
106454      * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday.
106455      */
106456
106457     /**
106458      * @cfg {RegExp} [disabledDatesRE=null]
106459      * JavaScript regular expression used to disable a pattern of dates. The {@link #disabledDates}
106460      * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
106461      * disabledDates value.
106462      */
106463
106464     /**
106465      * @cfg {String[]} disabledDates
106466      * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular expression so
106467      * they are very powerful. Some examples:
106468      *
106469      *   - ['03/08/2003', '09/16/2003'] would disable those exact dates
106470      *   - ['03/08', '09/16'] would disable those days for every year
106471      *   - ['^03/08'] would only match the beginning (useful if you are using short years)
106472      *   - ['03/../2006'] would disable every day in March 2006
106473      *   - ['^03'] would disable every day in every March
106474      *
106475      * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order
106476      * to support regular expressions, if you are using a date format that has '.' in it, you will have to escape the
106477      * dot when restricting dates. For example: ['03\\.08\\.03'].
106478      */
106479
106480     /**
106481      * @cfg {Boolean} disableAnim
106482      * True to disable animations when showing the month picker.
106483      */
106484     disableAnim: false,
106485
106486     /**
106487      * @cfg {String} [baseCls='x-datepicker']
106488      * The base CSS class to apply to this components element.
106489      */
106490     baseCls: Ext.baseCSSPrefix + 'datepicker',
106491
106492     /**
106493      * @cfg {String} [selectedCls='x-datepicker-selected']
106494      * The class to apply to the selected cell.
106495      */
106496
106497     /**
106498      * @cfg {String} [disabledCellCls='x-datepicker-disabled']
106499      * The class to apply to disabled cells.
106500      */
106501
106502     /**
106503      * @cfg {String} longDayFormat
106504      * The format for displaying a date in a longer format.
106505      */
106506     longDayFormat: 'F d, Y',
106507
106508     /**
106509      * @cfg {Object} keyNavConfig
106510      * Specifies optional custom key event handlers for the {@link Ext.util.KeyNav} attached to this date picker. Must
106511      * conform to the config format recognized by the {@link Ext.util.KeyNav} constructor. Handlers specified in this
106512      * object will replace default handlers of the same name.
106513      */
106514
106515     /**
106516      * @cfg {Boolean} focusOnShow
106517      * True to automatically focus the picker on show.
106518      */
106519     focusOnShow: false,
106520
106521     // private
106522     // Set by other components to stop the picker focus being updated when the value changes.
106523     focusOnSelect: true,
106524
106525     width: 178,
106526
106527     // default value used to initialise each date in the DatePicker
106528     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
106529     initHour: 12, // 24-hour format
106530
106531     numDays: 42,
106532
106533     // private, inherit docs
106534     initComponent : function() {
106535         var me = this,
106536             clearTime = Ext.Date.clearTime;
106537
106538         me.selectedCls = me.baseCls + '-selected';
106539         me.disabledCellCls = me.baseCls + '-disabled';
106540         me.prevCls = me.baseCls + '-prevday';
106541         me.activeCls = me.baseCls + '-active';
106542         me.nextCls = me.baseCls + '-prevday';
106543         me.todayCls = me.baseCls + '-today';
106544         me.dayNames = me.dayNames.slice(me.startDay).concat(me.dayNames.slice(0, me.startDay));
106545         this.callParent();
106546
106547         me.value = me.value ?
106548                  clearTime(me.value, true) : clearTime(new Date());
106549
106550         me.addEvents(
106551             /**
106552              * @event select
106553              * Fires when a date is selected
106554              * @param {Ext.picker.Date} this DatePicker
106555              * @param {Date} date The selected date
106556              */
106557             'select'
106558         );
106559
106560         me.initDisabledDays();
106561     },
106562
106563     // private, inherit docs
106564     onRender : function(container, position){
106565         /*
106566          * days array for looping through 6 full weeks (6 weeks * 7 days)
106567          * Note that we explicitly force the size here so the template creates
106568          * all the appropriate cells.
106569          */
106570
106571         var me = this,
106572             days = new Array(me.numDays),
106573             today = Ext.Date.format(new Date(), me.format);
106574
106575         Ext.applyIf(me, {
106576             renderData: {}
106577         });
106578
106579         Ext.apply(me.renderData, {
106580             dayNames: me.dayNames,
106581             ariaTitle: me.ariaTitle,
106582             value: me.value,
106583             showToday: me.showToday,
106584             prevText: me.prevText,
106585             nextText: me.nextText,
106586             days: days
106587         });
106588         me.getTpl('renderTpl').longDayFormat = me.longDayFormat;
106589
106590         me.addChildEls('eventEl', 'prevEl', 'nextEl', 'middleBtnEl', 'footerEl');
106591
106592         this.callParent(arguments);
106593         me.el.unselectable();
106594
106595         me.cells = me.eventEl.select('tbody td');
106596         me.textNodes = me.eventEl.query('tbody td span');
106597
106598         me.monthBtn = Ext.create('Ext.button.Split', {
106599             text: '',
106600             tooltip: me.monthYearText,
106601             renderTo: me.middleBtnEl
106602         });
106603         //~ me.middleBtnEl.down('button').addCls(Ext.baseCSSPrefix + 'btn-arrow');
106604
106605
106606         me.todayBtn = Ext.create('Ext.button.Button', {
106607             renderTo: me.footerEl,
106608             text: Ext.String.format(me.todayText, today),
106609             tooltip: Ext.String.format(me.todayTip, today),
106610             handler: me.selectToday,
106611             scope: me
106612         });
106613     },
106614
106615     // private, inherit docs
106616     initEvents: function(){
106617         var me = this,
106618             eDate = Ext.Date,
106619             day = eDate.DAY;
106620
106621         this.callParent();
106622
106623         me.prevRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
106624             handler: me.showPrevMonth,
106625             scope: me,
106626             preventDefault: true,
106627             stopDefault: true
106628         });
106629
106630         me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
106631             handler: me.showNextMonth,
106632             scope: me,
106633             preventDefault:true,
106634             stopDefault:true
106635         });
106636
106637         me.keyNav = Ext.create('Ext.util.KeyNav', me.eventEl, Ext.apply({
106638             scope: me,
106639             'left' : function(e){
106640                 if(e.ctrlKey){
106641                     me.showPrevMonth();
106642                 }else{
106643                     me.update(eDate.add(me.activeDate, day, -1));
106644                 }
106645             },
106646
106647             'right' : function(e){
106648                 if(e.ctrlKey){
106649                     me.showNextMonth();
106650                 }else{
106651                     me.update(eDate.add(me.activeDate, day, 1));
106652                 }
106653             },
106654
106655             'up' : function(e){
106656                 if(e.ctrlKey){
106657                     me.showNextYear();
106658                 }else{
106659                     me.update(eDate.add(me.activeDate, day, -7));
106660                 }
106661             },
106662
106663             'down' : function(e){
106664                 if(e.ctrlKey){
106665                     me.showPrevYear();
106666                 }else{
106667                     me.update(eDate.add(me.activeDate, day, 7));
106668                 }
106669             },
106670             'pageUp' : me.showNextMonth,
106671             'pageDown' : me.showPrevMonth,
106672             'enter' : function(e){
106673                 e.stopPropagation();
106674                 return true;
106675             }
106676         }, me.keyNavConfig));
106677
106678         if(me.showToday){
106679             me.todayKeyListener = me.eventEl.addKeyListener(Ext.EventObject.SPACE, me.selectToday,  me);
106680         }
106681         me.mon(me.eventEl, 'mousewheel', me.handleMouseWheel, me);
106682         me.mon(me.eventEl, 'click', me.handleDateClick,  me, {delegate: 'a.' + me.baseCls + '-date'});
106683         me.mon(me.monthBtn, 'click', me.showMonthPicker, me);
106684         me.mon(me.monthBtn, 'arrowclick', me.showMonthPicker, me);
106685         me.update(me.value);
106686     },
106687
106688     /**
106689      * Setup the disabled dates regex based on config options
106690      * @private
106691      */
106692     initDisabledDays : function(){
106693         var me = this,
106694             dd = me.disabledDates,
106695             re = '(?:',
106696             len;
106697
106698         if(!me.disabledDatesRE && dd){
106699                 len = dd.length - 1;
106700
106701             Ext.each(dd, function(d, i){
106702                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(Ext.Date.dateFormat(d, me.format)) + '$' : dd[i];
106703                 if(i != len){
106704                     re += '|';
106705                 }
106706             }, me);
106707             me.disabledDatesRE = new RegExp(re + ')');
106708         }
106709     },
106710
106711     /**
106712      * Replaces any existing disabled dates with new values and refreshes the DatePicker.
106713      * @param {String[]/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config for
106714      * details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
106715      * @return {Ext.picker.Date} this
106716      */
106717     setDisabledDates : function(dd){
106718         var me = this;
106719
106720         if(Ext.isArray(dd)){
106721             me.disabledDates = dd;
106722             me.disabledDatesRE = null;
106723         }else{
106724             me.disabledDatesRE = dd;
106725         }
106726         me.initDisabledDays();
106727         me.update(me.value, true);
106728         return me;
106729     },
106730
106731     /**
106732      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
106733      * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details
106734      * on supported values.
106735      * @return {Ext.picker.Date} this
106736      */
106737     setDisabledDays : function(dd){
106738         this.disabledDays = dd;
106739         return this.update(this.value, true);
106740     },
106741
106742     /**
106743      * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
106744      * @param {Date} value The minimum date that can be selected
106745      * @return {Ext.picker.Date} this
106746      */
106747     setMinDate : function(dt){
106748         this.minDate = dt;
106749         return this.update(this.value, true);
106750     },
106751
106752     /**
106753      * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
106754      * @param {Date} value The maximum date that can be selected
106755      * @return {Ext.picker.Date} this
106756      */
106757     setMaxDate : function(dt){
106758         this.maxDate = dt;
106759         return this.update(this.value, true);
106760     },
106761
106762     /**
106763      * Sets the value of the date field
106764      * @param {Date} value The date to set
106765      * @return {Ext.picker.Date} this
106766      */
106767     setValue : function(value){
106768         this.value = Ext.Date.clearTime(value, true);
106769         return this.update(this.value);
106770     },
106771
106772     /**
106773      * Gets the current selected value of the date field
106774      * @return {Date} The selected date
106775      */
106776     getValue : function(){
106777         return this.value;
106778     },
106779
106780     // private
106781     focus : function(){
106782         this.update(this.activeDate);
106783     },
106784
106785     // private, inherit docs
106786     onEnable: function(){
106787         this.callParent();
106788         this.setDisabledStatus(false);
106789         this.update(this.activeDate);
106790
106791     },
106792
106793     // private, inherit docs
106794     onDisable : function(){
106795         this.callParent();
106796         this.setDisabledStatus(true);
106797     },
106798
106799     /**
106800      * Set the disabled state of various internal components
106801      * @private
106802      * @param {Boolean} disabled
106803      */
106804     setDisabledStatus : function(disabled){
106805         var me = this;
106806
106807         me.keyNav.setDisabled(disabled);
106808         me.prevRepeater.setDisabled(disabled);
106809         me.nextRepeater.setDisabled(disabled);
106810         if (me.showToday) {
106811             me.todayKeyListener.setDisabled(disabled);
106812             me.todayBtn.setDisabled(disabled);
106813         }
106814     },
106815
106816     /**
106817      * Get the current active date.
106818      * @private
106819      * @return {Date} The active date
106820      */
106821     getActive: function(){
106822         return this.activeDate || this.value;
106823     },
106824
106825     /**
106826      * Run any animation required to hide/show the month picker.
106827      * @private
106828      * @param {Boolean} isHide True if it's a hide operation
106829      */
106830     runAnimation: function(isHide){
106831         var picker = this.monthPicker,
106832             options = {
106833                 duration: 200,
106834                 callback: function(){
106835                     if (isHide) {
106836                         picker.hide();
106837                     } else {
106838                         picker.show();
106839                     }
106840                 }
106841             };
106842
106843         if (isHide) {
106844             picker.el.slideOut('t', options);
106845         } else {
106846             picker.el.slideIn('t', options);
106847         }
106848     },
106849
106850     /**
106851      * Hides the month picker, if it's visible.
106852      * @param {Boolean} [animate] Indicates whether to animate this action. If the animate
106853      * parameter is not specified, the behavior will use {@link #disableAnim} to determine
106854      * whether to animate or not.
106855      * @return {Ext.picker.Date} this
106856      */
106857     hideMonthPicker : function(animate){
106858         var me = this,
106859             picker = me.monthPicker;
106860
106861         if (picker) {
106862             if (me.shouldAnimate(animate)) {
106863                 me.runAnimation(true);
106864             } else {
106865                 picker.hide();
106866             }
106867         }
106868         return me;
106869     },
106870
106871     /**
106872      * Show the month picker
106873      * @param {Boolean} [animate] Indicates whether to animate this action. If the animate
106874      * parameter is not specified, the behavior will use {@link #disableAnim} to determine
106875      * whether to animate or not.
106876      * @return {Ext.picker.Date} this
106877      */
106878     showMonthPicker : function(animate){
106879         var me = this,
106880             picker;
106881         
106882         if (me.rendered && !me.disabled) {
106883             picker = me.createMonthPicker();
106884             picker.setValue(me.getActive());
106885             picker.setSize(me.getSize());
106886             picker.setPosition(-1, -1);
106887             if (me.shouldAnimate(animate)) {
106888                 me.runAnimation(false);
106889             } else {
106890                 picker.show();
106891             }
106892         }
106893         return me;
106894     },
106895     
106896     /**
106897      * Checks whether a hide/show action should animate
106898      * @private
106899      * @param {Boolean} [animate] A possible animation value
106900      * @return {Boolean} Whether to animate the action
106901      */
106902     shouldAnimate: function(animate){
106903         return Ext.isDefined(animate) ? animate : !this.disableAnim;
106904     },
106905
106906     /**
106907      * Create the month picker instance
106908      * @private
106909      * @return {Ext.picker.Month} picker
106910      */
106911     createMonthPicker: function(){
106912         var me = this,
106913             picker = me.monthPicker;
106914
106915         if (!picker) {
106916             me.monthPicker = picker = Ext.create('Ext.picker.Month', {
106917                 renderTo: me.el,
106918                 floating: true,
106919                 shadow: false,
106920                 small: me.showToday === false,
106921                 listeners: {
106922                     scope: me,
106923                     cancelclick: me.onCancelClick,
106924                     okclick: me.onOkClick,
106925                     yeardblclick: me.onOkClick,
106926                     monthdblclick: me.onOkClick
106927                 }
106928             });
106929             if (!me.disableAnim) {
106930                 // hide the element if we're animating to prevent an initial flicker
106931                 picker.el.setStyle('display', 'none');
106932             }
106933             me.on('beforehide', Ext.Function.bind(me.hideMonthPicker, me, [false]));
106934         }
106935         return picker;
106936     },
106937
106938     /**
106939      * Respond to an ok click on the month picker
106940      * @private
106941      */
106942     onOkClick: function(picker, value){
106943         var me = this,
106944             month = value[0],
106945             year = value[1],
106946             date = new Date(year, month, me.getActive().getDate());
106947
106948         if (date.getMonth() !== month) {
106949             // 'fix' the JS rolling date conversion if needed
106950             date = new Date(year, month, 1).getLastDateOfMonth();
106951         }
106952         me.update(date);
106953         me.hideMonthPicker();
106954     },
106955
106956     /**
106957      * Respond to a cancel click on the month picker
106958      * @private
106959      */
106960     onCancelClick: function(){
106961         this.hideMonthPicker();
106962     },
106963
106964     /**
106965      * Show the previous month.
106966      * @param {Object} e
106967      * @return {Ext.picker.Date} this
106968      */
106969     showPrevMonth : function(e){
106970         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, -1));
106971     },
106972
106973     /**
106974      * Show the next month.
106975      * @param {Object} e
106976      * @return {Ext.picker.Date} this
106977      */
106978     showNextMonth : function(e){
106979         return this.update(Ext.Date.add(this.activeDate, Ext.Date.MONTH, 1));
106980     },
106981
106982     /**
106983      * Show the previous year.
106984      * @return {Ext.picker.Date} this
106985      */
106986     showPrevYear : function(){
106987         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, -1));
106988     },
106989
106990     /**
106991      * Show the next year.
106992      * @return {Ext.picker.Date} this
106993      */
106994     showNextYear : function(){
106995         this.update(Ext.Date.add(this.activeDate, Ext.Date.YEAR, 1));
106996     },
106997
106998     /**
106999      * Respond to the mouse wheel event
107000      * @private
107001      * @param {Ext.EventObject} e
107002      */
107003     handleMouseWheel : function(e){
107004         e.stopEvent();
107005         if(!this.disabled){
107006             var delta = e.getWheelDelta();
107007             if(delta > 0){
107008                 this.showPrevMonth();
107009             } else if(delta < 0){
107010                 this.showNextMonth();
107011             }
107012         }
107013     },
107014
107015     /**
107016      * Respond to a date being clicked in the picker
107017      * @private
107018      * @param {Ext.EventObject} e
107019      * @param {HTMLElement} t
107020      */
107021     handleDateClick : function(e, t){
107022         var me = this,
107023             handler = me.handler;
107024
107025         e.stopEvent();
107026         if(!me.disabled && t.dateValue && !Ext.fly(t.parentNode).hasCls(me.disabledCellCls)){
107027             me.cancelFocus = me.focusOnSelect === false;
107028             me.setValue(new Date(t.dateValue));
107029             delete me.cancelFocus;
107030             me.fireEvent('select', me, me.value);
107031             if (handler) {
107032                 handler.call(me.scope || me, me, me.value);
107033             }
107034             // event handling is turned off on hide
107035             // when we are using the picker in a field
107036             // therefore onSelect comes AFTER the select
107037             // event.
107038             me.onSelect();
107039         }
107040     },
107041
107042     /**
107043      * Perform any post-select actions
107044      * @private
107045      */
107046     onSelect: function() {
107047         if (this.hideOnSelect) {
107048              this.hide();
107049          }
107050     },
107051
107052     /**
107053      * Sets the current value to today.
107054      * @return {Ext.picker.Date} this
107055      */
107056     selectToday : function(){
107057         var me = this,
107058             btn = me.todayBtn,
107059             handler = me.handler;
107060
107061         if(btn && !btn.disabled){
107062             me.setValue(Ext.Date.clearTime(new Date()));
107063             me.fireEvent('select', me, me.value);
107064             if (handler) {
107065                 handler.call(me.scope || me, me, me.value);
107066             }
107067             me.onSelect();
107068         }
107069         return me;
107070     },
107071
107072     /**
107073      * Update the selected cell
107074      * @private
107075      * @param {Date} date The new date
107076      * @param {Date} active The active date
107077      */
107078     selectedUpdate: function(date, active){
107079         var me = this,
107080             t = date.getTime(),
107081             cells = me.cells,
107082             cls = me.selectedCls;
107083
107084         cells.removeCls(cls);
107085         cells.each(function(c){
107086             if (c.dom.firstChild.dateValue == t) {
107087                 me.el.dom.setAttribute('aria-activedescendent', c.dom.id);
107088                 c.addCls(cls);
107089                 if(me.isVisible() && !me.cancelFocus){
107090                     Ext.fly(c.dom.firstChild).focus(50);
107091                 }
107092                 return false;
107093             }
107094         }, this);
107095     },
107096
107097     /**
107098      * Update the contents of the picker for a new month
107099      * @private
107100      * @param {Date} date The new date
107101      * @param {Date} active The active date
107102      */
107103     fullUpdate: function(date, active){
107104         var me = this,
107105             cells = me.cells.elements,
107106             textNodes = me.textNodes,
107107             disabledCls = me.disabledCellCls,
107108             eDate = Ext.Date,
107109             i = 0,
107110             extraDays = 0,
107111             visible = me.isVisible(),
107112             sel = +eDate.clearTime(date, true),
107113             today = +eDate.clearTime(new Date()),
107114             min = me.minDate ? eDate.clearTime(me.minDate, true) : Number.NEGATIVE_INFINITY,
107115             max = me.maxDate ? eDate.clearTime(me.maxDate, true) : Number.POSITIVE_INFINITY,
107116             ddMatch = me.disabledDatesRE,
107117             ddText = me.disabledDatesText,
107118             ddays = me.disabledDays ? me.disabledDays.join('') : false,
107119             ddaysText = me.disabledDaysText,
107120             format = me.format,
107121             days = eDate.getDaysInMonth(date),
107122             firstOfMonth = eDate.getFirstDateOfMonth(date),
107123             startingPos = firstOfMonth.getDay() - me.startDay,
107124             previousMonth = eDate.add(date, eDate.MONTH, -1),
107125             longDayFormat = me.longDayFormat,
107126             prevStart,
107127             current,
107128             disableToday,
107129             tempDate,
107130             setCellClass,
107131             html,
107132             cls,
107133             formatValue,
107134             value;
107135
107136         if (startingPos < 0) {
107137             startingPos += 7;
107138         }
107139
107140         days += startingPos;
107141         prevStart = eDate.getDaysInMonth(previousMonth) - startingPos;
107142         current = new Date(previousMonth.getFullYear(), previousMonth.getMonth(), prevStart, me.initHour);
107143
107144         if (me.showToday) {
107145             tempDate = eDate.clearTime(new Date());
107146             disableToday = (tempDate < min || tempDate > max ||
107147                 (ddMatch && format && ddMatch.test(eDate.dateFormat(tempDate, format))) ||
107148                 (ddays && ddays.indexOf(tempDate.getDay()) != -1));
107149
107150             if (!me.disabled) {
107151                 me.todayBtn.setDisabled(disableToday);
107152                 me.todayKeyListener.setDisabled(disableToday);
107153             }
107154         }
107155
107156         setCellClass = function(cell){
107157             value = +eDate.clearTime(current, true);
107158             cell.title = eDate.format(current, longDayFormat);
107159             // store dateValue number as an expando
107160             cell.firstChild.dateValue = value;
107161             if(value == today){
107162                 cell.className += ' ' + me.todayCls;
107163                 cell.title = me.todayText;
107164             }
107165             if(value == sel){
107166                 cell.className += ' ' + me.selectedCls;
107167                 me.el.dom.setAttribute('aria-activedescendant', cell.id);
107168                 if (visible && me.floating) {
107169                     Ext.fly(cell.firstChild).focus(50);
107170                 }
107171             }
107172             // disabling
107173             if(value < min) {
107174                 cell.className = disabledCls;
107175                 cell.title = me.minText;
107176                 return;
107177             }
107178             if(value > max) {
107179                 cell.className = disabledCls;
107180                 cell.title = me.maxText;
107181                 return;
107182             }
107183             if(ddays){
107184                 if(ddays.indexOf(current.getDay()) != -1){
107185                     cell.title = ddaysText;
107186                     cell.className = disabledCls;
107187                 }
107188             }
107189             if(ddMatch && format){
107190                 formatValue = eDate.dateFormat(current, format);
107191                 if(ddMatch.test(formatValue)){
107192                     cell.title = ddText.replace('%0', formatValue);
107193                     cell.className = disabledCls;
107194                 }
107195             }
107196         };
107197
107198         for(; i < me.numDays; ++i) {
107199             if (i < startingPos) {
107200                 html = (++prevStart);
107201                 cls = me.prevCls;
107202             } else if (i >= days) {
107203                 html = (++extraDays);
107204                 cls = me.nextCls;
107205             } else {
107206                 html = i - startingPos + 1;
107207                 cls = me.activeCls;
107208             }
107209             textNodes[i].innerHTML = html;
107210             cells[i].className = cls;
107211             current.setDate(current.getDate() + 1);
107212             setCellClass(cells[i]);
107213         }
107214
107215         me.monthBtn.setText(me.monthNames[date.getMonth()] + ' ' + date.getFullYear());
107216     },
107217
107218     /**
107219      * Update the contents of the picker
107220      * @private
107221      * @param {Date} date The new date
107222      * @param {Boolean} forceRefresh True to force a full refresh
107223      */
107224     update : function(date, forceRefresh){
107225         var me = this,
107226             active = me.activeDate;
107227
107228         if (me.rendered) {
107229             me.activeDate = date;
107230             if(!forceRefresh && active && me.el && active.getMonth() == date.getMonth() && active.getFullYear() == date.getFullYear()){
107231                 me.selectedUpdate(date, active);
107232             } else {
107233                 me.fullUpdate(date, active);
107234             }
107235         }
107236         return me;
107237     },
107238
107239     // private, inherit docs
107240     beforeDestroy : function() {
107241         var me = this;
107242
107243         if (me.rendered) {
107244             Ext.destroy(
107245                 me.todayKeyListener,
107246                 me.keyNav,
107247                 me.monthPicker,
107248                 me.monthBtn,
107249                 me.nextRepeater,
107250                 me.prevRepeater,
107251                 me.todayBtn
107252             );
107253             delete me.textNodes;
107254             delete me.cells.elements;
107255         }
107256         me.callParent();
107257     },
107258
107259     // private, inherit docs
107260     onShow: function() {
107261         this.callParent(arguments);
107262         if (this.focusOnShow) {
107263             this.focus();
107264         }
107265     }
107266 },
107267
107268 // After dependencies have loaded:
107269 function() {
107270     var proto = this.prototype;
107271
107272     proto.monthNames = Ext.Date.monthNames;
107273
107274     proto.dayNames = Ext.Date.dayNames;
107275
107276     proto.format = Ext.Date.defaultFormat;
107277 });
107278
107279 /**
107280  * @docauthor Jason Johnston <jason@sencha.com>
107281  *
107282  * Provides a date input field with a {@link Ext.picker.Date date picker} dropdown and automatic date
107283  * validation.
107284  *
107285  * This field recognizes and uses the JavaScript Date object as its main {@link #value} type. In addition,
107286  * it recognizes string values which are parsed according to the {@link #format} and/or {@link #altFormats}
107287  * configs. These may be reconfigured to use date formats appropriate for the user's locale.
107288  *
107289  * The field may be limited to a certain range of dates by using the {@link #minValue}, {@link #maxValue},
107290  * {@link #disabledDays}, and {@link #disabledDates} config parameters. These configurations will be used both
107291  * in the field's validation, and in the date picker dropdown by preventing invalid dates from being selected.
107292  *
107293  * # Example usage
107294  *
107295  *     @example
107296  *     Ext.create('Ext.form.Panel', {
107297  *         renderTo: Ext.getBody(),
107298  *         width: 300,
107299  *         bodyPadding: 10,
107300  *         title: 'Dates',
107301  *         items: [{
107302  *             xtype: 'datefield',
107303  *             anchor: '100%',
107304  *             fieldLabel: 'From',
107305  *             name: 'from_date',
107306  *             maxValue: new Date()  // limited to the current date or prior
107307  *         }, {
107308  *             xtype: 'datefield',
107309  *             anchor: '100%',
107310  *             fieldLabel: 'To',
107311  *             name: 'to_date',
107312  *             value: new Date()  // defaults to today
107313  *         }]
107314  *     });
107315  *
107316  * # Date Formats Examples
107317  *
107318  * This example shows a couple of different date format parsing scenarios. Both use custom date format
107319  * configurations; the first one matches the configured `format` while the second matches the `altFormats`.
107320  *
107321  *     @example
107322  *     Ext.create('Ext.form.Panel', {
107323  *         renderTo: Ext.getBody(),
107324  *         width: 300,
107325  *         bodyPadding: 10,
107326  *         title: 'Dates',
107327  *         items: [{
107328  *             xtype: 'datefield',
107329  *             anchor: '100%',
107330  *             fieldLabel: 'Date',
107331  *             name: 'date',
107332  *             // The value matches the format; will be parsed and displayed using that format.
107333  *             format: 'm d Y',
107334  *             value: '2 4 1978'
107335  *         }, {
107336  *             xtype: 'datefield',
107337  *             anchor: '100%',
107338  *             fieldLabel: 'Date',
107339  *             name: 'date',
107340  *             // The value does not match the format, but does match an altFormat; will be parsed
107341  *             // using the altFormat and displayed using the format.
107342  *             format: 'm d Y',
107343  *             altFormats: 'm,d,Y|m.d.Y',
107344  *             value: '2.4.1978'
107345  *         }]
107346  *     });
107347  */
107348 Ext.define('Ext.form.field.Date', {
107349     extend:'Ext.form.field.Picker',
107350     alias: 'widget.datefield',
107351     requires: ['Ext.picker.Date'],
107352     alternateClassName: ['Ext.form.DateField', 'Ext.form.Date'],
107353
107354     /**
107355      * @cfg {String} format
107356      * The default date format string which can be overriden for localization support. The format must be valid
107357      * according to {@link Ext.Date#parse}.
107358      */
107359     format : "m/d/Y",
107360     /**
107361      * @cfg {String} altFormats
107362      * Multiple date formats separated by "|" to try when parsing a user input value and it does not match the defined
107363      * format.
107364      */
107365     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",
107366     /**
107367      * @cfg {String} disabledDaysText
107368      * The tooltip to display when the date falls on a disabled day.
107369      */
107370     disabledDaysText : "Disabled",
107371     /**
107372      * @cfg {String} disabledDatesText
107373      * The tooltip text to display when the date falls on a disabled date.
107374      */
107375     disabledDatesText : "Disabled",
107376     /**
107377      * @cfg {String} minText
107378      * The error text to display when the date in the cell is before {@link #minValue}.
107379      */
107380     minText : "The date in this field must be equal to or after {0}",
107381     /**
107382      * @cfg {String} maxText
107383      * The error text to display when the date in the cell is after {@link #maxValue}.
107384      */
107385     maxText : "The date in this field must be equal to or before {0}",
107386     /**
107387      * @cfg {String} invalidText
107388      * The error text to display when the date in the field is invalid.
107389      */
107390     invalidText : "{0} is not a valid date - it must be in the format {1}",
107391     /**
107392      * @cfg {String} [triggerCls='x-form-date-trigger']
107393      * An additional CSS class used to style the trigger button. The trigger will always get the class 'x-form-trigger'
107394      * and triggerCls will be **appended** if specified (default class displays a calendar icon).
107395      */
107396     triggerCls : Ext.baseCSSPrefix + 'form-date-trigger',
107397     /**
107398      * @cfg {Boolean} showToday
107399      * false to hide the footer area of the Date picker containing the Today button and disable the keyboard handler for
107400      * spacebar that selects the current date.
107401      */
107402     showToday : true,
107403     /**
107404      * @cfg {Date/String} minValue
107405      * The minimum allowed date. Can be either a Javascript date object or a string date in a valid format.
107406      */
107407     /**
107408      * @cfg {Date/String} maxValue
107409      * The maximum allowed date. Can be either a Javascript date object or a string date in a valid format.
107410      */
107411     /**
107412      * @cfg {Number[]} disabledDays
107413      * An array of days to disable, 0 based. Some examples:
107414      *
107415      *     // disable Sunday and Saturday:
107416      *     disabledDays:  [0, 6]
107417      *     // disable weekdays:
107418      *     disabledDays: [1,2,3,4,5]
107419      */
107420     /**
107421      * @cfg {String[]} disabledDates
107422      * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular expression so
107423      * they are very powerful. Some examples:
107424      *
107425      *     // disable these exact dates:
107426      *     disabledDates: ["03/08/2003", "09/16/2003"]
107427      *     // disable these days for every year:
107428      *     disabledDates: ["03/08", "09/16"]
107429      *     // only match the beginning (useful if you are using short years):
107430      *     disabledDates: ["^03/08"]
107431      *     // disable every day in March 2006:
107432      *     disabledDates: ["03/../2006"]
107433      *     // disable every day in every March:
107434      *     disabledDates: ["^03"]
107435      *
107436      * Note that the format of the dates included in the array should exactly match the {@link #format} config. In order
107437      * to support regular expressions, if you are using a {@link #format date format} that has "." in it, you will have
107438      * to escape the dot when restricting dates. For example: `["03\\.08\\.03"]`.
107439      */
107440
107441     /**
107442      * @cfg {String} submitFormat
107443      * The date format string which will be submitted to the server. The format must be valid according to {@link
107444      * Ext.Date#parse} (defaults to {@link #format}).
107445      */
107446
107447     // in the absence of a time value, a default value of 12 noon will be used
107448     // (note: 12 noon was chosen because it steers well clear of all DST timezone changes)
107449     initTime: '12', // 24 hour format
107450
107451     initTimeFormat: 'H',
107452
107453     matchFieldWidth: false,
107454     /**
107455      * @cfg {Number} startDay
107456      * Day index at which the week should begin, 0-based (defaults to Sunday)
107457      */
107458     startDay: 0,
107459
107460     initComponent : function(){
107461         var me = this,
107462             isString = Ext.isString,
107463             min, max;
107464
107465         min = me.minValue;
107466         max = me.maxValue;
107467         if(isString(min)){
107468             me.minValue = me.parseDate(min);
107469         }
107470         if(isString(max)){
107471             me.maxValue = me.parseDate(max);
107472         }
107473         me.disabledDatesRE = null;
107474         me.initDisabledDays();
107475
107476         me.callParent();
107477     },
107478
107479     initValue: function() {
107480         var me = this,
107481             value = me.value;
107482
107483         // If a String value was supplied, try to convert it to a proper Date
107484         if (Ext.isString(value)) {
107485             me.value = me.rawToValue(value);
107486         }
107487
107488         me.callParent();
107489     },
107490
107491     // private
107492     initDisabledDays : function(){
107493         if(this.disabledDates){
107494             var dd = this.disabledDates,
107495                 len = dd.length - 1,
107496                 re = "(?:";
107497
107498             Ext.each(dd, function(d, i){
107499                 re += Ext.isDate(d) ? '^' + Ext.String.escapeRegex(d.dateFormat(this.format)) + '$' : dd[i];
107500                 if (i !== len) {
107501                     re += '|';
107502                 }
107503             }, this);
107504             this.disabledDatesRE = new RegExp(re + ')');
107505         }
107506     },
107507
107508     /**
107509      * Replaces any existing disabled dates with new values and refreshes the Date picker.
107510      * @param {String[]} disabledDates An array of date strings (see the {@link #disabledDates} config for details on
107511      * supported values) used to disable a pattern of dates.
107512      */
107513     setDisabledDates : function(dd){
107514         var me = this,
107515             picker = me.picker;
107516
107517         me.disabledDates = dd;
107518         me.initDisabledDays();
107519         if (picker) {
107520             picker.setDisabledDates(me.disabledDatesRE);
107521         }
107522     },
107523
107524     /**
107525      * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the Date picker.
107526      * @param {Number[]} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config for details on
107527      * supported values.
107528      */
107529     setDisabledDays : function(dd){
107530         var picker = this.picker;
107531
107532         this.disabledDays = dd;
107533         if (picker) {
107534             picker.setDisabledDays(dd);
107535         }
107536     },
107537
107538     /**
107539      * Replaces any existing {@link #minValue} with the new value and refreshes the Date picker.
107540      * @param {Date} value The minimum date that can be selected
107541      */
107542     setMinValue : function(dt){
107543         var me = this,
107544             picker = me.picker,
107545             minValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
107546
107547         me.minValue = minValue;
107548         if (picker) {
107549             picker.minText = Ext.String.format(me.minText, me.formatDate(me.minValue));
107550             picker.setMinDate(minValue);
107551         }
107552     },
107553
107554     /**
107555      * Replaces any existing {@link #maxValue} with the new value and refreshes the Date picker.
107556      * @param {Date} value The maximum date that can be selected
107557      */
107558     setMaxValue : function(dt){
107559         var me = this,
107560             picker = me.picker,
107561             maxValue = (Ext.isString(dt) ? me.parseDate(dt) : dt);
107562
107563         me.maxValue = maxValue;
107564         if (picker) {
107565             picker.maxText = Ext.String.format(me.maxText, me.formatDate(me.maxValue));
107566             picker.setMaxDate(maxValue);
107567         }
107568     },
107569
107570     /**
107571      * Runs all of Date's validations and returns an array of any errors. Note that this first runs Text's validations,
107572      * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
107573      * the date format is valid, that the chosen date is within the min and max date constraints set, that the date
107574      * chosen is not in the disabledDates regex and that the day chosed is not one of the disabledDays.
107575      * @param {Object} [value] The value to get errors for (defaults to the current field value)
107576      * @return {String[]} All validation errors for this field
107577      */
107578     getErrors: function(value) {
107579         var me = this,
107580             format = Ext.String.format,
107581             clearTime = Ext.Date.clearTime,
107582             errors = me.callParent(arguments),
107583             disabledDays = me.disabledDays,
107584             disabledDatesRE = me.disabledDatesRE,
107585             minValue = me.minValue,
107586             maxValue = me.maxValue,
107587             len = disabledDays ? disabledDays.length : 0,
107588             i = 0,
107589             svalue,
107590             fvalue,
107591             day,
107592             time;
107593
107594         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
107595
107596         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
107597              return errors;
107598         }
107599
107600         svalue = value;
107601         value = me.parseDate(value);
107602         if (!value) {
107603             errors.push(format(me.invalidText, svalue, me.format));
107604             return errors;
107605         }
107606
107607         time = value.getTime();
107608         if (minValue && time < clearTime(minValue).getTime()) {
107609             errors.push(format(me.minText, me.formatDate(minValue)));
107610         }
107611
107612         if (maxValue && time > clearTime(maxValue).getTime()) {
107613             errors.push(format(me.maxText, me.formatDate(maxValue)));
107614         }
107615
107616         if (disabledDays) {
107617             day = value.getDay();
107618
107619             for(; i < len; i++) {
107620                 if (day === disabledDays[i]) {
107621                     errors.push(me.disabledDaysText);
107622                     break;
107623                 }
107624             }
107625         }
107626
107627         fvalue = me.formatDate(value);
107628         if (disabledDatesRE && disabledDatesRE.test(fvalue)) {
107629             errors.push(format(me.disabledDatesText, fvalue));
107630         }
107631
107632         return errors;
107633     },
107634
107635     rawToValue: function(rawValue) {
107636         return this.parseDate(rawValue) || rawValue || null;
107637     },
107638
107639     valueToRaw: function(value) {
107640         return this.formatDate(this.parseDate(value));
107641     },
107642
107643     /**
107644      * @method setValue
107645      * Sets the value of the date field. You can pass a date object or any string that can be parsed into a valid date,
107646      * using {@link #format} as the date format, according to the same rules as {@link Ext.Date#parse} (the default
107647      * format used is "m/d/Y").
107648      *
107649      * Usage:
107650      *
107651      *     //All of these calls set the same date value (May 4, 2006)
107652      *
107653      *     //Pass a date object:
107654      *     var dt = new Date('5/4/2006');
107655      *     dateField.setValue(dt);
107656      *
107657      *     //Pass a date string (default format):
107658      *     dateField.setValue('05/04/2006');
107659      *
107660      *     //Pass a date string (custom format):
107661      *     dateField.format = 'Y-m-d';
107662      *     dateField.setValue('2006-05-04');
107663      *
107664      * @param {String/Date} date The date or valid date string
107665      * @return {Ext.form.field.Date} this
107666      */
107667
107668     /**
107669      * Attempts to parse a given string value using a given {@link Ext.Date#parse date format}.
107670      * @param {String} value The value to attempt to parse
107671      * @param {String} format A valid date format (see {@link Ext.Date#parse})
107672      * @return {Date} The parsed Date object, or null if the value could not be successfully parsed.
107673      */
107674     safeParse : function(value, format) {
107675         var me = this,
107676             utilDate = Ext.Date,
107677             parsedDate,
107678             result = null;
107679
107680         if (utilDate.formatContainsHourInfo(format)) {
107681             // if parse format contains hour information, no DST adjustment is necessary
107682             result = utilDate.parse(value, format);
107683         } else {
107684             // set time to 12 noon, then clear the time
107685             parsedDate = utilDate.parse(value + ' ' + me.initTime, format + ' ' + me.initTimeFormat);
107686             if (parsedDate) {
107687                 result = utilDate.clearTime(parsedDate);
107688             }
107689         }
107690         return result;
107691     },
107692
107693     // @private
107694     getSubmitValue: function() {
107695         var format = this.submitFormat || this.format,
107696             value = this.getValue();
107697
107698         return value ? Ext.Date.format(value, format) : '';
107699     },
107700
107701     /**
107702      * @private
107703      */
107704     parseDate : function(value) {
107705         if(!value || Ext.isDate(value)){
107706             return value;
107707         }
107708
107709         var me = this,
107710             val = me.safeParse(value, me.format),
107711             altFormats = me.altFormats,
107712             altFormatsArray = me.altFormatsArray,
107713             i = 0,
107714             len;
107715
107716         if (!val && altFormats) {
107717             altFormatsArray = altFormatsArray || altFormats.split('|');
107718             len = altFormatsArray.length;
107719             for (; i < len && !val; ++i) {
107720                 val = me.safeParse(value, altFormatsArray[i]);
107721             }
107722         }
107723         return val;
107724     },
107725
107726     // private
107727     formatDate : function(date){
107728         return Ext.isDate(date) ? Ext.Date.dateFormat(date, this.format) : date;
107729     },
107730
107731     createPicker: function() {
107732         var me = this,
107733             format = Ext.String.format;
107734
107735         return Ext.create('Ext.picker.Date', {
107736             pickerField: me,
107737             ownerCt: me.ownerCt,
107738             renderTo: document.body,
107739             floating: true,
107740             hidden: true,
107741             focusOnShow: true,
107742             minDate: me.minValue,
107743             maxDate: me.maxValue,
107744             disabledDatesRE: me.disabledDatesRE,
107745             disabledDatesText: me.disabledDatesText,
107746             disabledDays: me.disabledDays,
107747             disabledDaysText: me.disabledDaysText,
107748             format: me.format,
107749             showToday: me.showToday,
107750             startDay: me.startDay,
107751             minText: format(me.minText, me.formatDate(me.minValue)),
107752             maxText: format(me.maxText, me.formatDate(me.maxValue)),
107753             listeners: {
107754                 scope: me,
107755                 select: me.onSelect
107756             },
107757             keyNavConfig: {
107758                 esc: function() {
107759                     me.collapse();
107760                 }
107761             }
107762         });
107763     },
107764
107765     onSelect: function(m, d) {
107766         var me = this;
107767
107768         me.setValue(d);
107769         me.fireEvent('select', me, d);
107770         me.collapse();
107771     },
107772
107773     /**
107774      * @private
107775      * Sets the Date picker's value to match the current field value when expanding.
107776      */
107777     onExpand: function() {
107778         var value = this.getValue();
107779         this.picker.setValue(Ext.isDate(value) ? value : new Date());
107780     },
107781
107782     /**
107783      * @private
107784      * Focuses the field when collapsing the Date picker.
107785      */
107786     onCollapse: function() {
107787         this.focus(false, 60);
107788     },
107789
107790     // private
107791     beforeBlur : function(){
107792         var me = this,
107793             v = me.parseDate(me.getRawValue()),
107794             focusTask = me.focusTask;
107795
107796         if (focusTask) {
107797             focusTask.cancel();
107798         }
107799
107800         if (v) {
107801             me.setValue(v);
107802         }
107803     }
107804
107805     /**
107806      * @hide
107807      * @cfg {Boolean} grow
107808      */
107809     /**
107810      * @hide
107811      * @cfg {Number} growMin
107812      */
107813     /**
107814      * @hide
107815      * @cfg {Number} growMax
107816      */
107817     /**
107818      * @hide
107819      * @method autoSize
107820      */
107821 });
107822
107823 /**
107824  * A display-only text field which is not validated and not submitted. This is useful for when you want to display a
107825  * 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
107826  * value. The value can be optionally {@link #htmlEncode HTML encoded} if it contains HTML markup that you do not want
107827  * to be rendered.
107828  *
107829  * If you have more complex content, or need to include components within the displayed content, also consider using a
107830  * {@link Ext.form.FieldContainer} instead.
107831  *
107832  * Example:
107833  *
107834  *     @example
107835  *     Ext.create('Ext.form.Panel', {
107836  *         renderTo: Ext.getBody(),
107837  *         width: 175,
107838  *         height: 120,
107839  *         bodyPadding: 10,
107840  *         title: 'Final Score',
107841  *         items: [{
107842  *             xtype: 'displayfield',
107843  *             fieldLabel: 'Home',
107844  *             name: 'home_score',
107845  *             value: '10'
107846  *         }, {
107847  *             xtype: 'displayfield',
107848  *             fieldLabel: 'Visitor',
107849  *             name: 'visitor_score',
107850  *             value: '11'
107851  *         }],
107852  *         buttons: [{
107853  *             text: 'Update',
107854  *         }]
107855  *     });
107856  */
107857 Ext.define('Ext.form.field.Display', {
107858     extend:'Ext.form.field.Base',
107859     alias: 'widget.displayfield',
107860     requires: ['Ext.util.Format', 'Ext.XTemplate'],
107861     alternateClassName: ['Ext.form.DisplayField', 'Ext.form.Display'],
107862     fieldSubTpl: [
107863         '<div id="{id}" class="{fieldCls}"></div>',
107864         {
107865             compiled: true,
107866             disableFormats: true
107867         }
107868     ],
107869
107870     /**
107871      * @cfg {String} [fieldCls="x-form-display-field"]
107872      * The default CSS class for the field.
107873      */
107874     fieldCls: Ext.baseCSSPrefix + 'form-display-field',
107875
107876     /**
107877      * @cfg {Boolean} htmlEncode
107878      * false to skip HTML-encoding the text when rendering it. This might be useful if you want to
107879      * include tags in the field's innerHTML rather than rendering them as string literals per the default logic.
107880      */
107881     htmlEncode: false,
107882
107883     validateOnChange: false,
107884
107885     initEvents: Ext.emptyFn,
107886
107887     submitValue: false,
107888
107889     isValid: function() {
107890         return true;
107891     },
107892
107893     validate: function() {
107894         return true;
107895     },
107896
107897     getRawValue: function() {
107898         return this.rawValue;
107899     },
107900
107901     setRawValue: function(value) {
107902         var me = this;
107903         value = Ext.value(value, '');
107904         me.rawValue = value;
107905         if (me.rendered) {
107906             me.inputEl.dom.innerHTML = me.htmlEncode ? Ext.util.Format.htmlEncode(value) : value;
107907         }
107908         return value;
107909     },
107910
107911     // private
107912     getContentTarget: function() {
107913         return this.inputEl;
107914     }
107915
107916     /**
107917      * @cfg {String} inputType
107918      * @hide
107919      */
107920     /**
107921      * @cfg {Boolean} disabled
107922      * @hide
107923      */
107924     /**
107925      * @cfg {Boolean} readOnly
107926      * @hide
107927      */
107928     /**
107929      * @cfg {Boolean} validateOnChange
107930      * @hide
107931      */
107932     /**
107933      * @cfg {Number} checkChangeEvents
107934      * @hide
107935      */
107936     /**
107937      * @cfg {Number} checkChangeBuffer
107938      * @hide
107939      */
107940 });
107941
107942 /**
107943  * @docauthor Jason Johnston <jason@sencha.com>
107944  *
107945  * A file upload field which has custom styling and allows control over the button text and other
107946  * features of {@link Ext.form.field.Text text fields} like {@link Ext.form.field.Text#emptyText empty text}.
107947  * It uses a hidden file input element behind the scenes to allow user selection of a file and to
107948  * perform the actual upload during {@link Ext.form.Basic#submit form submit}.
107949  *
107950  * Because there is no secure cross-browser way to programmatically set the value of a file input,
107951  * the standard Field `setValue` method is not implemented. The `{@link #getValue}` method will return
107952  * a value that is browser-dependent; some have just the file name, some have a full path, some use
107953  * a fake path.
107954  *
107955  * **IMPORTANT:** File uploads are not performed using normal 'Ajax' techniques; see the description for
107956  * {@link Ext.form.Basic#hasUpload} for details.
107957  *
107958  * # Example Usage
107959  *
107960  *     @example
107961  *     Ext.create('Ext.form.Panel', {
107962  *         title: 'Upload a Photo',
107963  *         width: 400,
107964  *         bodyPadding: 10,
107965  *         frame: true,
107966  *         renderTo: Ext.getBody(),
107967  *         items: [{
107968  *             xtype: 'filefield',
107969  *             name: 'photo',
107970  *             fieldLabel: 'Photo',
107971  *             labelWidth: 50,
107972  *             msgTarget: 'side',
107973  *             allowBlank: false,
107974  *             anchor: '100%',
107975  *             buttonText: 'Select Photo...'
107976  *         }],
107977  *
107978  *         buttons: [{
107979  *             text: 'Upload',
107980  *             handler: function() {
107981  *                 var form = this.up('form').getForm();
107982  *                 if(form.isValid()){
107983  *                     form.submit({
107984  *                         url: 'photo-upload.php',
107985  *                         waitMsg: 'Uploading your photo...',
107986  *                         success: function(fp, o) {
107987  *                             Ext.Msg.alert('Success', 'Your photo "' + o.result.file + '" has been uploaded.');
107988  *                         }
107989  *                     });
107990  *                 }
107991  *             }
107992  *         }]
107993  *     });
107994  */
107995 Ext.define("Ext.form.field.File", {
107996     extend: 'Ext.form.field.Text',
107997     alias: ['widget.filefield', 'widget.fileuploadfield'],
107998     alternateClassName: ['Ext.form.FileUploadField', 'Ext.ux.form.FileUploadField', 'Ext.form.File'],
107999     uses: ['Ext.button.Button', 'Ext.layout.component.field.File'],
108000
108001     /**
108002      * @cfg {String} buttonText
108003      * The button text to display on the upload button. Note that if you supply a value for
108004      * {@link #buttonConfig}, the buttonConfig.text value will be used instead if available.
108005      */
108006     buttonText: 'Browse...',
108007
108008     /**
108009      * @cfg {Boolean} buttonOnly
108010      * True to display the file upload field as a button with no visible text field. If true, all
108011      * inherited Text members will still be available.
108012      */
108013     buttonOnly: false,
108014
108015     /**
108016      * @cfg {Number} buttonMargin
108017      * The number of pixels of space reserved between the button and the text field. Note that this only
108018      * applies if {@link #buttonOnly} = false.
108019      */
108020     buttonMargin: 3,
108021
108022     /**
108023      * @cfg {Object} buttonConfig
108024      * A standard {@link Ext.button.Button} config object.
108025      */
108026
108027     /**
108028      * @event change
108029      * Fires when the underlying file input field's value has changed from the user selecting a new file from the system
108030      * file selection dialog.
108031      * @param {Ext.ux.form.FileUploadField} this
108032      * @param {String} value The file value returned by the underlying file input field
108033      */
108034
108035     /**
108036      * @property {Ext.Element} fileInputEl
108037      * A reference to the invisible file input element created for this upload field. Only populated after this
108038      * component is rendered.
108039      */
108040
108041     /**
108042      * @property {Ext.button.Button} button
108043      * A reference to the trigger Button component created for this upload field. Only populated after this component is
108044      * rendered.
108045      */
108046
108047     /**
108048      * @cfg {String} [fieldBodyCls='x-form-file-wrap']
108049      * An extra CSS class to be applied to the body content element in addition to {@link #fieldBodyCls}.
108050      */
108051     fieldBodyCls: Ext.baseCSSPrefix + 'form-file-wrap',
108052
108053     /**
108054      * @cfg {Boolean} readOnly
108055      * Unlike with other form fields, the readOnly config defaults to true in File field.
108056      */
108057     readOnly: true,
108058
108059     // private
108060     componentLayout: 'filefield',
108061
108062     // private
108063     onRender: function() {
108064         var me = this,
108065             inputEl;
108066
108067         me.callParent(arguments);
108068
108069         me.createButton();
108070         me.createFileInput();
108071         
108072         // we don't create the file/button til after onRender, the initial disable() is
108073         // called in the onRender of the component.
108074         if (me.disabled) {
108075             me.disableItems();
108076         }
108077
108078         inputEl = me.inputEl;
108079         inputEl.dom.removeAttribute('name'); //name goes on the fileInput, not the text input
108080         if (me.buttonOnly) {
108081             inputEl.setDisplayed(false);
108082         }
108083     },
108084
108085     /**
108086      * @private
108087      * Creates the custom trigger Button component. The fileInput will be inserted into this.
108088      */
108089     createButton: function() {
108090         var me = this;
108091         me.button = Ext.widget('button', Ext.apply({
108092             ui: me.ui,
108093             renderTo: me.bodyEl,
108094             text: me.buttonText,
108095             cls: Ext.baseCSSPrefix + 'form-file-btn',
108096             preventDefault: false,
108097             style: me.buttonOnly ? '' : 'margin-left:' + me.buttonMargin + 'px'
108098         }, me.buttonConfig));
108099     },
108100
108101     /**
108102      * @private
108103      * Creates the file input element. It is inserted into the trigger button component, made
108104      * invisible, and floated on top of the button's other content so that it will receive the
108105      * button's clicks.
108106      */
108107     createFileInput : function() {
108108         var me = this;
108109         me.fileInputEl = me.button.el.createChild({
108110             name: me.getName(),
108111             cls: Ext.baseCSSPrefix + 'form-file-input',
108112             tag: 'input',
108113             type: 'file',
108114             size: 1
108115         }).on('change', me.onFileChange, me);
108116     },
108117
108118     /**
108119      * @private Event handler fired when the user selects a file.
108120      */
108121     onFileChange: function() {
108122         this.lastValue = null; // force change event to get fired even if the user selects a file with the same name
108123         Ext.form.field.File.superclass.setValue.call(this, this.fileInputEl.dom.value);
108124     },
108125
108126     /**
108127      * Overridden to do nothing
108128      * @hide
108129      */
108130     setValue: Ext.emptyFn,
108131
108132     reset : function(){
108133         var me = this;
108134         if (me.rendered) {
108135             me.fileInputEl.remove();
108136             me.createFileInput();
108137             me.inputEl.dom.value = '';
108138         }
108139         me.callParent();
108140     },
108141
108142     onDisable: function(){
108143         this.callParent();
108144         this.disableItems();
108145     },
108146     
108147     disableItems: function(){
108148         var file = this.fileInputEl,
108149             button = this.button;
108150              
108151         if (file) {
108152             file.dom.disabled = true;
108153         }
108154         if (button) {
108155             button.disable();
108156         }    
108157     },
108158
108159     onEnable: function(){
108160         var me = this;
108161         me.callParent();
108162         me.fileInputEl.dom.disabled = false;
108163         me.button.enable();
108164     },
108165
108166     isFileUpload: function() {
108167         return true;
108168     },
108169
108170     extractFileInput: function() {
108171         var fileInput = this.fileInputEl.dom;
108172         this.reset();
108173         return fileInput;
108174     },
108175
108176     onDestroy: function(){
108177         Ext.destroyMembers(this, 'fileInputEl', 'button');
108178         this.callParent();
108179     }
108180
108181
108182 });
108183
108184 /**
108185  * A basic hidden field for storing hidden values in forms that need to be passed in the form submit.
108186  *
108187  * This creates an actual input element with type="submit" in the DOM. While its label is
108188  * {@link #hideLabel not rendered} by default, it is still a real component and may be sized according
108189  * to its owner container's layout.
108190  *
108191  * Because of this, in most cases it is more convenient and less problematic to simply
108192  * {@link Ext.form.action.Action#params pass hidden parameters} directly when
108193  * {@link Ext.form.Basic#submit submitting the form}.
108194  *
108195  * Example:
108196  *
108197  *     new Ext.form.Panel({
108198  *         title: 'My Form',
108199  *         items: [{
108200  *             xtype: 'textfield',
108201  *             fieldLabel: 'Text Field',
108202  *             name: 'text_field',
108203  *             value: 'value from text field'
108204  *         }, {
108205  *             xtype: 'hiddenfield',
108206  *             name: 'hidden_field_1',
108207  *             value: 'value from hidden field'
108208  *         }],
108209  *
108210  *         buttons: [{
108211  *             text: 'Submit',
108212  *             handler: function() {
108213  *                 this.up('form').getForm().submit({
108214  *                     params: {
108215  *                         hidden_field_2: 'value from submit call'
108216  *                     }
108217  *                 });
108218  *             }
108219  *         }]
108220  *     });
108221  *
108222  * Submitting the above form will result in three values sent to the server:
108223  *
108224  *     text_field=value+from+text+field&hidden;_field_1=value+from+hidden+field&hidden_field_2=value+from+submit+call
108225  *
108226  */
108227 Ext.define('Ext.form.field.Hidden', {
108228     extend:'Ext.form.field.Base',
108229     alias: ['widget.hiddenfield', 'widget.hidden'],
108230     alternateClassName: 'Ext.form.Hidden',
108231
108232     // private
108233     inputType : 'hidden',
108234     hideLabel: true,
108235     
108236     initComponent: function(){
108237         this.formItemCls += '-hidden';
108238         this.callParent();    
108239     },
108240     
108241     /**
108242      * @private
108243      * Override. Treat undefined and null values as equal to an empty string value.
108244      */
108245     isEqual: function(value1, value2) {
108246         return this.isEqualAsString(value1, value2);
108247     },
108248
108249     // These are all private overrides
108250     initEvents: Ext.emptyFn,
108251     setSize : Ext.emptyFn,
108252     setWidth : Ext.emptyFn,
108253     setHeight : Ext.emptyFn,
108254     setPosition : Ext.emptyFn,
108255     setPagePosition : Ext.emptyFn,
108256     markInvalid : Ext.emptyFn,
108257     clearInvalid : Ext.emptyFn
108258 });
108259
108260 /**
108261  * Color picker provides a simple color palette for choosing colors. The picker can be rendered to any container. The
108262  * available default to a standard 40-color palette; this can be customized with the {@link #colors} config.
108263  *
108264  * Typically you will need to implement a handler function to be notified when the user chooses a color from the picker;
108265  * you can register the handler using the {@link #select} event, or by implementing the {@link #handler} method.
108266  *
108267  *     @example
108268  *     Ext.create('Ext.picker.Color', {
108269  *         value: '993300',  // initial selected color
108270  *         renderTo: Ext.getBody(),
108271  *         listeners: {
108272  *             select: function(picker, selColor) {
108273  *                 alert(selColor);
108274  *             }
108275  *         }
108276  *     });
108277  */
108278 Ext.define('Ext.picker.Color', {
108279     extend: 'Ext.Component',
108280     requires: 'Ext.XTemplate',
108281     alias: 'widget.colorpicker',
108282     alternateClassName: 'Ext.ColorPalette',
108283
108284     /**
108285      * @cfg {String} [componentCls='x-color-picker']
108286      * The CSS class to apply to the containing element.
108287      */
108288     componentCls : Ext.baseCSSPrefix + 'color-picker',
108289
108290     /**
108291      * @cfg {String} [selectedCls='x-color-picker-selected']
108292      * The CSS class to apply to the selected element
108293      */
108294     selectedCls: Ext.baseCSSPrefix + 'color-picker-selected',
108295
108296     /**
108297      * @cfg {String} value
108298      * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol). Note that the hex
108299      * codes are case-sensitive.
108300      */
108301     value : null,
108302
108303     /**
108304      * @cfg {String} clickEvent
108305      * The DOM event that will cause a color to be selected. This can be any valid event name (dblclick, contextmenu).
108306      */
108307     clickEvent :'click',
108308
108309     /**
108310      * @cfg {Boolean} allowReselect
108311      * If set to true then reselecting a color that is already selected fires the {@link #select} event
108312      */
108313     allowReselect : false,
108314
108315     /**
108316      * @property {String[]} colors
108317      * An array of 6-digit color hex code strings (without the # symbol). This array can contain any number of colors,
108318      * and each hex code should be unique. The width of the picker is controlled via CSS by adjusting the width property
108319      * of the 'x-color-picker' class (or assigning a custom class), so you can balance the number of colors with the
108320      * width setting until the box is symmetrical.
108321      *
108322      * You can override individual colors if needed:
108323      *
108324      *     var cp = new Ext.picker.Color();
108325      *     cp.colors[0] = 'FF0000';  // change the first box to red
108326      *
108327      * Or you can provide a custom array of your own for complete control:
108328      *
108329      *     var cp = new Ext.picker.Color();
108330      *     cp.colors = ['000000', '993300', '333300'];
108331      */
108332     colors : [
108333         '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333',
108334         '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080',
108335         'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696',
108336         'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0',
108337         'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF'
108338     ],
108339
108340     /**
108341      * @cfg {Function} handler
108342      * A function that will handle the select event of this picker. The handler is passed the following parameters:
108343      *
108344      * - `picker` : ColorPicker
108345      *
108346      *   The {@link Ext.picker.Color picker}.
108347      *
108348      * - `color` : String
108349      *
108350      *   The 6-digit color hex code (without the # symbol).
108351      */
108352
108353     /**
108354      * @cfg {Object} scope
108355      * The scope (`this` reference) in which the `{@link #handler}` function will be called. Defaults to this
108356      * Color picker instance.
108357      */
108358
108359     colorRe: /(?:^|\s)color-(.{6})(?:\s|$)/,
108360     
108361     renderTpl: [
108362         '<tpl for="colors">',
108363             '<a href="#" class="color-{.}" hidefocus="on">',
108364                 '<em><span style="background:#{.}" unselectable="on">&#160;</span></em>',
108365             '</a>',
108366         '</tpl>'
108367     ],
108368
108369     // private
108370     initComponent : function(){
108371         var me = this;
108372
108373         me.callParent(arguments);
108374         me.addEvents(
108375             /**
108376              * @event select
108377              * Fires when a color is selected
108378              * @param {Ext.picker.Color} this
108379              * @param {String} color The 6-digit color hex code (without the # symbol)
108380              */
108381             'select'
108382         );
108383
108384         if (me.handler) {
108385             me.on('select', me.handler, me.scope, true);
108386         }
108387     },
108388
108389
108390     // private
108391     onRender : function(container, position){
108392         var me = this,
108393             clickEvent = me.clickEvent;
108394
108395         Ext.apply(me.renderData, {
108396             itemCls: me.itemCls,
108397             colors: me.colors
108398         });
108399         me.callParent(arguments);
108400
108401         me.mon(me.el, clickEvent, me.handleClick, me, {delegate: 'a'});
108402         // always stop following the anchors
108403         if(clickEvent != 'click'){
108404             me.mon(me.el, 'click', Ext.emptyFn, me, {delegate: 'a', stopEvent: true});
108405         }
108406     },
108407
108408     // private
108409     afterRender : function(){
108410         var me = this,
108411             value;
108412
108413         me.callParent(arguments);
108414         if (me.value) {
108415             value = me.value;
108416             me.value = null;
108417             me.select(value, true);
108418         }
108419     },
108420
108421     // private
108422     handleClick : function(event, target){
108423         var me = this,
108424             color;
108425
108426         event.stopEvent();
108427         if (!me.disabled) {
108428             color = target.className.match(me.colorRe)[1];
108429             me.select(color.toUpperCase());
108430         }
108431     },
108432
108433     /**
108434      * Selects the specified color in the picker (fires the {@link #select} event)
108435      * @param {String} color A valid 6-digit color hex code (# will be stripped if included)
108436      * @param {Boolean} suppressEvent (optional) True to stop the select event from firing. Defaults to false.
108437      */
108438     select : function(color, suppressEvent){
108439
108440         var me = this,
108441             selectedCls = me.selectedCls,
108442             value = me.value,
108443             el;
108444
108445         color = color.replace('#', '');
108446         if (!me.rendered) {
108447             me.value = color;
108448             return;
108449         }
108450
108451
108452         if (color != value || me.allowReselect) {
108453             el = me.el;
108454
108455             if (me.value) {
108456                 el.down('a.color-' + value).removeCls(selectedCls);
108457             }
108458             el.down('a.color-' + color).addCls(selectedCls);
108459             me.value = color;
108460             if (suppressEvent !== true) {
108461                 me.fireEvent('select', me, color);
108462             }
108463         }
108464     },
108465
108466     /**
108467      * Get the currently selected color value.
108468      * @return {String} value The selected value. Null if nothing is selected.
108469      */
108470     getValue: function(){
108471         return this.value || null;
108472     }
108473 });
108474
108475 /**
108476  * @private
108477  * @class Ext.layout.component.field.HtmlEditor
108478  * @extends Ext.layout.component.field.Field
108479  * Layout class for {@link Ext.form.field.HtmlEditor} fields. Sizes the toolbar, textarea, and iframe elements.
108480  * @private
108481  */
108482
108483 Ext.define('Ext.layout.component.field.HtmlEditor', {
108484     extend: 'Ext.layout.component.field.Field',
108485     alias: ['layout.htmleditor'],
108486
108487     type: 'htmleditor',
108488
108489     sizeBodyContents: function(width, height) {
108490         var me = this,
108491             owner = me.owner,
108492             bodyEl = owner.bodyEl,
108493             toolbar = owner.getToolbar(),
108494             textarea = owner.textareaEl,
108495             iframe = owner.iframeEl,
108496             editorHeight;
108497
108498         if (Ext.isNumber(width)) {
108499             width -= bodyEl.getFrameWidth('lr');
108500         }
108501         toolbar.setWidth(width);
108502         textarea.setWidth(width);
108503         iframe.setWidth(width);
108504
108505         // If fixed height, subtract toolbar height from the input area height
108506         if (Ext.isNumber(height)) {
108507             editorHeight = height - toolbar.getHeight() - bodyEl.getFrameWidth('tb');
108508             textarea.setHeight(editorHeight);
108509             iframe.setHeight(editorHeight);
108510         }
108511     }
108512 });
108513 /**
108514  * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
108515  * automatically hidden when needed. These are noted in the config options where appropriate.
108516  *
108517  * The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not
108518  * enabled by default unless the global {@link Ext.tip.QuickTipManager} singleton is
108519  * {@link Ext.tip.QuickTipManager#init initialized}.
108520  *
108521  * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an
108522  * Editor within any element that has display set to 'none' can cause problems in Safari and Firefox due to their
108523  * default iframe reloading bugs.
108524  *
108525  * # Example usage
108526  *
108527  * Simple example rendered with default options:
108528  *
108529  *     @example
108530  *     Ext.tip.QuickTipManager.init();  // enable tooltips
108531  *     Ext.create('Ext.form.HtmlEditor', {
108532  *         width: 580,
108533  *         height: 250,
108534  *         renderTo: Ext.getBody()
108535  *     });
108536  *
108537  * Passed via xtype into a container and with custom options:
108538  *
108539  *     @example
108540  *     Ext.tip.QuickTipManager.init();  // enable tooltips
108541  *     new Ext.panel.Panel({
108542  *         title: 'HTML Editor',
108543  *         renderTo: Ext.getBody(),
108544  *         width: 550,
108545  *         height: 250,
108546  *         frame: true,
108547  *         layout: 'fit',
108548  *         items: {
108549  *             xtype: 'htmleditor',
108550  *             enableColors: false,
108551  *             enableAlignments: false
108552  *         }
108553  *     });
108554  */
108555 Ext.define('Ext.form.field.HtmlEditor', {
108556     extend:'Ext.Component',
108557     mixins: {
108558         labelable: 'Ext.form.Labelable',
108559         field: 'Ext.form.field.Field'
108560     },
108561     alias: 'widget.htmleditor',
108562     alternateClassName: 'Ext.form.HtmlEditor',
108563     requires: [
108564         'Ext.tip.QuickTipManager',
108565         'Ext.picker.Color',
108566         'Ext.toolbar.Item',
108567         'Ext.toolbar.Toolbar',
108568         'Ext.util.Format',
108569         'Ext.layout.component.field.HtmlEditor'
108570     ],
108571
108572     fieldSubTpl: [
108573         '<div id="{cmpId}-toolbarWrap" class="{toolbarWrapCls}"></div>',
108574         '<textarea id="{cmpId}-textareaEl" name="{name}" tabIndex="-1" class="{textareaCls}" ',
108575             'style="{size}" autocomplete="off"></textarea>',
108576         '<iframe id="{cmpId}-iframeEl" name="{iframeName}" frameBorder="0" style="overflow:auto;{size}" src="{iframeSrc}"></iframe>',
108577         {
108578             compiled: true,
108579             disableFormats: true
108580         }
108581     ],
108582
108583     /**
108584      * @cfg {Boolean} enableFormat
108585      * Enable the bold, italic and underline buttons
108586      */
108587     enableFormat : true,
108588     /**
108589      * @cfg {Boolean} enableFontSize
108590      * Enable the increase/decrease font size buttons
108591      */
108592     enableFontSize : true,
108593     /**
108594      * @cfg {Boolean} enableColors
108595      * Enable the fore/highlight color buttons
108596      */
108597     enableColors : true,
108598     /**
108599      * @cfg {Boolean} enableAlignments
108600      * Enable the left, center, right alignment buttons
108601      */
108602     enableAlignments : true,
108603     /**
108604      * @cfg {Boolean} enableLists
108605      * Enable the bullet and numbered list buttons. Not available in Safari.
108606      */
108607     enableLists : true,
108608     /**
108609      * @cfg {Boolean} enableSourceEdit
108610      * Enable the switch to source edit button. Not available in Safari.
108611      */
108612     enableSourceEdit : true,
108613     /**
108614      * @cfg {Boolean} enableLinks
108615      * Enable the create link button. Not available in Safari.
108616      */
108617     enableLinks : true,
108618     /**
108619      * @cfg {Boolean} enableFont
108620      * Enable font selection. Not available in Safari.
108621      */
108622     enableFont : true,
108623     /**
108624      * @cfg {String} createLinkText
108625      * The default text for the create link prompt
108626      */
108627     createLinkText : 'Please enter the URL for the link:',
108628     /**
108629      * @cfg {String} [defaultLinkValue='http://']
108630      * The default value for the create link prompt
108631      */
108632     defaultLinkValue : 'http:/'+'/',
108633     /**
108634      * @cfg {String[]} fontFamilies
108635      * An array of available font families
108636      */
108637     fontFamilies : [
108638         'Arial',
108639         'Courier New',
108640         'Tahoma',
108641         'Times New Roman',
108642         'Verdana'
108643     ],
108644     defaultFont: 'tahoma',
108645     /**
108646      * @cfg {String} defaultValue
108647      * A default value to be put into the editor to resolve focus issues (defaults to (Non-breaking space) in Opera
108648      * and IE6, â€‹(Zero-width space) in all other browsers).
108649      */
108650     defaultValue: (Ext.isOpera || Ext.isIE6) ? '&#160;' : '&#8203;',
108651
108652     fieldBodyCls: Ext.baseCSSPrefix + 'html-editor-wrap',
108653
108654     componentLayout: 'htmleditor',
108655
108656     // private properties
108657     initialized : false,
108658     activated : false,
108659     sourceEditMode : false,
108660     iframePad:3,
108661     hideMode:'offsets',
108662
108663     maskOnDisable: true,
108664
108665     // private
108666     initComponent : function(){
108667         var me = this;
108668
108669         me.addEvents(
108670             /**
108671              * @event initialize
108672              * Fires when the editor is fully initialized (including the iframe)
108673              * @param {Ext.form.field.HtmlEditor} this
108674              */
108675             'initialize',
108676             /**
108677              * @event activate
108678              * Fires when the editor is first receives the focus. Any insertion must wait until after this event.
108679              * @param {Ext.form.field.HtmlEditor} this
108680              */
108681             'activate',
108682              /**
108683              * @event beforesync
108684              * Fires before the textarea is updated with content from the editor iframe. Return false to cancel the
108685              * sync.
108686              * @param {Ext.form.field.HtmlEditor} this
108687              * @param {String} html
108688              */
108689             'beforesync',
108690              /**
108691              * @event beforepush
108692              * Fires before the iframe editor is updated with content from the textarea. Return false to cancel the
108693              * push.
108694              * @param {Ext.form.field.HtmlEditor} this
108695              * @param {String} html
108696              */
108697             'beforepush',
108698              /**
108699              * @event sync
108700              * Fires when the textarea is updated with content from the editor iframe.
108701              * @param {Ext.form.field.HtmlEditor} this
108702              * @param {String} html
108703              */
108704             'sync',
108705              /**
108706              * @event push
108707              * Fires when the iframe editor is updated with content from the textarea.
108708              * @param {Ext.form.field.HtmlEditor} this
108709              * @param {String} html
108710              */
108711             'push',
108712              /**
108713              * @event editmodechange
108714              * Fires when the editor switches edit modes
108715              * @param {Ext.form.field.HtmlEditor} this
108716              * @param {Boolean} sourceEdit True if source edit, false if standard editing.
108717              */
108718             'editmodechange'
108719         );
108720
108721         me.callParent(arguments);
108722
108723         // Init mixins
108724         me.initLabelable();
108725         me.initField();
108726     },
108727
108728     /**
108729      * Called when the editor creates its toolbar. Override this method if you need to
108730      * add custom toolbar buttons.
108731      * @param {Ext.form.field.HtmlEditor} editor
108732      * @protected
108733      */
108734     createToolbar : function(editor){
108735         var me = this,
108736             items = [],
108737             tipsEnabled = Ext.tip.QuickTipManager && Ext.tip.QuickTipManager.isEnabled(),
108738             baseCSSPrefix = Ext.baseCSSPrefix,
108739             fontSelectItem, toolbar, undef;
108740
108741         function btn(id, toggle, handler){
108742             return {
108743                 itemId : id,
108744                 cls : baseCSSPrefix + 'btn-icon',
108745                 iconCls: baseCSSPrefix + 'edit-'+id,
108746                 enableToggle:toggle !== false,
108747                 scope: editor,
108748                 handler:handler||editor.relayBtnCmd,
108749                 clickEvent:'mousedown',
108750                 tooltip: tipsEnabled ? editor.buttonTips[id] || undef : undef,
108751                 overflowText: editor.buttonTips[id].title || undef,
108752                 tabIndex:-1
108753             };
108754         }
108755
108756
108757         if (me.enableFont && !Ext.isSafari2) {
108758             fontSelectItem = Ext.widget('component', {
108759                 renderTpl: [
108760                     '<select id="{id}-selectEl" class="{cls}">',
108761                         '<tpl for="fonts">',
108762                             '<option value="{[values.toLowerCase()]}" style="font-family:{.}"<tpl if="values.toLowerCase()==parent.defaultFont"> selected</tpl>>{.}</option>',
108763                         '</tpl>',
108764                     '</select>'
108765                 ],
108766                 renderData: {
108767                     cls: baseCSSPrefix + 'font-select',
108768                     fonts: me.fontFamilies,
108769                     defaultFont: me.defaultFont
108770                 },
108771                 childEls: ['selectEl'],
108772                 onDisable: function() {
108773                     var selectEl = this.selectEl;
108774                     if (selectEl) {
108775                         selectEl.dom.disabled = true;
108776                     }
108777                     Ext.Component.superclass.onDisable.apply(this, arguments);
108778                 },
108779                 onEnable: function() {
108780                     var selectEl = this.selectEl;
108781                     if (selectEl) {
108782                         selectEl.dom.disabled = false;
108783                     }
108784                     Ext.Component.superclass.onEnable.apply(this, arguments);
108785                 }
108786             });
108787
108788             items.push(
108789                 fontSelectItem,
108790                 '-'
108791             );
108792         }
108793
108794         if (me.enableFormat) {
108795             items.push(
108796                 btn('bold'),
108797                 btn('italic'),
108798                 btn('underline')
108799             );
108800         }
108801
108802         if (me.enableFontSize) {
108803             items.push(
108804                 '-',
108805                 btn('increasefontsize', false, me.adjustFont),
108806                 btn('decreasefontsize', false, me.adjustFont)
108807             );
108808         }
108809
108810         if (me.enableColors) {
108811             items.push(
108812                 '-', {
108813                     itemId: 'forecolor',
108814                     cls: baseCSSPrefix + 'btn-icon',
108815                     iconCls: baseCSSPrefix + 'edit-forecolor',
108816                     overflowText: editor.buttonTips.forecolor.title,
108817                     tooltip: tipsEnabled ? editor.buttonTips.forecolor || undef : undef,
108818                     tabIndex:-1,
108819                     menu : Ext.widget('menu', {
108820                         plain: true,
108821                         items: [{
108822                             xtype: 'colorpicker',
108823                             allowReselect: true,
108824                             focus: Ext.emptyFn,
108825                             value: '000000',
108826                             plain: true,
108827                             clickEvent: 'mousedown',
108828                             handler: function(cp, color) {
108829                                 me.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
108830                                 me.deferFocus();
108831                                 this.up('menu').hide();
108832                             }
108833                         }]
108834                     })
108835                 }, {
108836                     itemId: 'backcolor',
108837                     cls: baseCSSPrefix + 'btn-icon',
108838                     iconCls: baseCSSPrefix + 'edit-backcolor',
108839                     overflowText: editor.buttonTips.backcolor.title,
108840                     tooltip: tipsEnabled ? editor.buttonTips.backcolor || undef : undef,
108841                     tabIndex:-1,
108842                     menu : Ext.widget('menu', {
108843                         plain: true,
108844                         items: [{
108845                             xtype: 'colorpicker',
108846                             focus: Ext.emptyFn,
108847                             value: 'FFFFFF',
108848                             plain: true,
108849                             allowReselect: true,
108850                             clickEvent: 'mousedown',
108851                             handler: function(cp, color) {
108852                                 if (Ext.isGecko) {
108853                                     me.execCmd('useCSS', false);
108854                                     me.execCmd('hilitecolor', color);
108855                                     me.execCmd('useCSS', true);
108856                                     me.deferFocus();
108857                                 } else {
108858                                     me.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
108859                                     me.deferFocus();
108860                                 }
108861                                 this.up('menu').hide();
108862                             }
108863                         }]
108864                     })
108865                 }
108866             );
108867         }
108868
108869         if (me.enableAlignments) {
108870             items.push(
108871                 '-',
108872                 btn('justifyleft'),
108873                 btn('justifycenter'),
108874                 btn('justifyright')
108875             );
108876         }
108877
108878         if (!Ext.isSafari2) {
108879             if (me.enableLinks) {
108880                 items.push(
108881                     '-',
108882                     btn('createlink', false, me.createLink)
108883                 );
108884             }
108885
108886             if (me.enableLists) {
108887                 items.push(
108888                     '-',
108889                     btn('insertorderedlist'),
108890                     btn('insertunorderedlist')
108891                 );
108892             }
108893             if (me.enableSourceEdit) {
108894                 items.push(
108895                     '-',
108896                     btn('sourceedit', true, function(btn){
108897                         me.toggleSourceEdit(!me.sourceEditMode);
108898                     })
108899                 );
108900             }
108901         }
108902
108903         // build the toolbar
108904         toolbar = Ext.widget('toolbar', {
108905             renderTo: me.toolbarWrap,
108906             enableOverflow: true,
108907             items: items
108908         });
108909
108910         if (fontSelectItem) {
108911             me.fontSelect = fontSelectItem.selectEl;
108912
108913             me.mon(me.fontSelect, 'change', function(){
108914                 me.relayCmd('fontname', me.fontSelect.dom.value);
108915                 me.deferFocus();
108916             });
108917         }
108918
108919         // stop form submits
108920         me.mon(toolbar.el, 'click', function(e){
108921             e.preventDefault();
108922         });
108923
108924         me.toolbar = toolbar;
108925     },
108926
108927     onDisable: function() {
108928         this.bodyEl.mask();
108929         this.callParent(arguments);
108930     },
108931
108932     onEnable: function() {
108933         this.bodyEl.unmask();
108934         this.callParent(arguments);
108935     },
108936
108937     /**
108938      * Sets the read only state of this field.
108939      * @param {Boolean} readOnly Whether the field should be read only.
108940      */
108941     setReadOnly: function(readOnly) {
108942         var me = this,
108943             textareaEl = me.textareaEl,
108944             iframeEl = me.iframeEl,
108945             body;
108946
108947         me.readOnly = readOnly;
108948
108949         if (textareaEl) {
108950             textareaEl.dom.readOnly = readOnly;
108951         }
108952
108953         if (me.initialized) {
108954             body = me.getEditorBody();
108955             if (Ext.isIE) {
108956                 // Hide the iframe while setting contentEditable so it doesn't grab focus
108957                 iframeEl.setDisplayed(false);
108958                 body.contentEditable = !readOnly;
108959                 iframeEl.setDisplayed(true);
108960             } else {
108961                 me.setDesignMode(!readOnly);
108962             }
108963             if (body) {
108964                 body.style.cursor = readOnly ? 'default' : 'text';
108965             }
108966             me.disableItems(readOnly);
108967         }
108968     },
108969
108970     /**
108971      * Called when the editor initializes the iframe with HTML contents. Override this method if you
108972      * want to change the initialization markup of the iframe (e.g. to add stylesheets).
108973      *
108974      * **Note:** IE8-Standards has unwanted scroller behavior, so the default meta tag forces IE7 compatibility.
108975      * Also note that forcing IE7 mode works when the page is loaded normally, but if you are using IE's Web
108976      * Developer Tools to manually set the document mode, that will take precedence and override what this
108977      * code sets by default. This can be confusing when developing, but is not a user-facing issue.
108978      * @protected
108979      */
108980     getDocMarkup: function() {
108981         var me = this,
108982             h = me.iframeEl.getHeight() - me.iframePad * 2;
108983         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);
108984     },
108985
108986     // private
108987     getEditorBody: function() {
108988         var doc = this.getDoc();
108989         return doc.body || doc.documentElement;
108990     },
108991
108992     // private
108993     getDoc: function() {
108994         return (!Ext.isIE && this.iframeEl.dom.contentDocument) || this.getWin().document;
108995     },
108996
108997     // private
108998     getWin: function() {
108999         return Ext.isIE ? this.iframeEl.dom.contentWindow : window.frames[this.iframeEl.dom.name];
109000     },
109001
109002     // private
109003     onRender: function() {
109004         var me = this;
109005
109006         me.onLabelableRender();
109007
109008         me.addChildEls('toolbarWrap', 'iframeEl', 'textareaEl');
109009
109010         me.callParent(arguments);
109011
109012         me.textareaEl.dom.value = me.value || '';
109013
109014         // Start polling for when the iframe document is ready to be manipulated
109015         me.monitorTask = Ext.TaskManager.start({
109016             run: me.checkDesignMode,
109017             scope: me,
109018             interval:100
109019         });
109020
109021         me.createToolbar(me);
109022         me.disableItems(true);
109023     },
109024
109025     initRenderTpl: function() {
109026         var me = this;
109027         if (!me.hasOwnProperty('renderTpl')) {
109028             me.renderTpl = me.getTpl('labelableRenderTpl');
109029         }
109030         return me.callParent();
109031     },
109032
109033     initRenderData: function() {
109034         return Ext.applyIf(this.callParent(), this.getLabelableRenderData());
109035     },
109036
109037     getSubTplData: function() {
109038         var cssPrefix = Ext.baseCSSPrefix;
109039         return {
109040             cmpId: this.id,
109041             id: this.getInputId(),
109042             toolbarWrapCls: cssPrefix + 'html-editor-tb',
109043             textareaCls: cssPrefix + 'hidden',
109044             iframeName: Ext.id(),
109045             iframeSrc: Ext.SSL_SECURE_URL,
109046             size: 'height:100px;'
109047         };
109048     },
109049
109050     getSubTplMarkup: function() {
109051         var data = this.getSubTplData();
109052         return this.getTpl('fieldSubTpl').apply(data);
109053     },
109054
109055     getBodyNaturalWidth: function() {
109056         return 565;
109057     },
109058
109059     initFrameDoc: function() {
109060         var me = this,
109061             doc, task;
109062
109063         Ext.TaskManager.stop(me.monitorTask);
109064
109065         doc = me.getDoc();
109066         me.win = me.getWin();
109067
109068         doc.open();
109069         doc.write(me.getDocMarkup());
109070         doc.close();
109071
109072         task = { // must defer to wait for browser to be ready
109073             run: function() {
109074                 var doc = me.getDoc();
109075                 if (doc.body || doc.readyState === 'complete') {
109076                     Ext.TaskManager.stop(task);
109077                     me.setDesignMode(true);
109078                     Ext.defer(me.initEditor, 10, me);
109079                 }
109080             },
109081             interval : 10,
109082             duration:10000,
109083             scope: me
109084         };
109085         Ext.TaskManager.start(task);
109086     },
109087
109088     checkDesignMode: function() {
109089         var me = this,
109090             doc = me.getDoc();
109091         if (doc && (!doc.editorInitialized || me.getDesignMode() !== 'on')) {
109092             me.initFrameDoc();
109093         }
109094     },
109095
109096     /**
109097      * @private
109098      * Sets current design mode. To enable, mode can be true or 'on', off otherwise
109099      */
109100     setDesignMode: function(mode) {
109101         var me = this,
109102             doc = me.getDoc();
109103         if (doc) {
109104             if (me.readOnly) {
109105                 mode = false;
109106             }
109107             doc.designMode = (/on|true/i).test(String(mode).toLowerCase()) ?'on':'off';
109108         }
109109     },
109110
109111     // private
109112     getDesignMode: function() {
109113         var doc = this.getDoc();
109114         return !doc ? '' : String(doc.designMode).toLowerCase();
109115     },
109116
109117     disableItems: function(disabled) {
109118         this.getToolbar().items.each(function(item){
109119             if(item.getItemId() !== 'sourceedit'){
109120                 item.setDisabled(disabled);
109121             }
109122         });
109123     },
109124
109125     /**
109126      * Toggles the editor between standard and source edit mode.
109127      * @param {Boolean} sourceEditMode (optional) True for source edit, false for standard
109128      */
109129     toggleSourceEdit: function(sourceEditMode) {
109130         var me = this,
109131             iframe = me.iframeEl,
109132             textarea = me.textareaEl,
109133             hiddenCls = Ext.baseCSSPrefix + 'hidden',
109134             btn = me.getToolbar().getComponent('sourceedit');
109135
109136         if (!Ext.isBoolean(sourceEditMode)) {
109137             sourceEditMode = !me.sourceEditMode;
109138         }
109139         me.sourceEditMode = sourceEditMode;
109140
109141         if (btn.pressed !== sourceEditMode) {
109142             btn.toggle(sourceEditMode);
109143         }
109144         if (sourceEditMode) {
109145             me.disableItems(true);
109146             me.syncValue();
109147             iframe.addCls(hiddenCls);
109148             textarea.removeCls(hiddenCls);
109149             textarea.dom.removeAttribute('tabIndex');
109150             textarea.focus();
109151         }
109152         else {
109153             if (me.initialized) {
109154                 me.disableItems(me.readOnly);
109155             }
109156             me.pushValue();
109157             iframe.removeCls(hiddenCls);
109158             textarea.addCls(hiddenCls);
109159             textarea.dom.setAttribute('tabIndex', -1);
109160             me.deferFocus();
109161         }
109162         me.fireEvent('editmodechange', me, sourceEditMode);
109163         me.doComponentLayout();
109164     },
109165
109166     // private used internally
109167     createLink : function() {
109168         var url = prompt(this.createLinkText, this.defaultLinkValue);
109169         if (url && url !== 'http:/'+'/') {
109170             this.relayCmd('createlink', url);
109171         }
109172     },
109173
109174     clearInvalid: Ext.emptyFn,
109175
109176     // docs inherit from Field
109177     setValue: function(value) {
109178         var me = this,
109179             textarea = me.textareaEl;
109180         me.mixins.field.setValue.call(me, value);
109181         if (value === null || value === undefined) {
109182             value = '';
109183         }
109184         if (textarea) {
109185             textarea.dom.value = value;
109186         }
109187         me.pushValue();
109188         return this;
109189     },
109190
109191     /**
109192      * If you need/want custom HTML cleanup, this is the method you should override.
109193      * @param {String} html The HTML to be cleaned
109194      * @return {String} The cleaned HTML
109195      * @protected
109196      */
109197     cleanHtml: function(html) {
109198         html = String(html);
109199         if (Ext.isWebKit) { // strip safari nonsense
109200             html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
109201         }
109202
109203         /*
109204          * Neat little hack. Strips out all the non-digit characters from the default
109205          * value and compares it to the character code of the first character in the string
109206          * because it can cause encoding issues when posted to the server.
109207          */
109208         if (html.charCodeAt(0) === this.defaultValue.replace(/\D/g, '')) {
109209             html = html.substring(1);
109210         }
109211         return html;
109212     },
109213
109214     /**
109215      * Syncs the contents of the editor iframe with the textarea.
109216      * @protected
109217      */
109218     syncValue : function(){
109219         var me = this,
109220             body, html, bodyStyle, match;
109221         if (me.initialized) {
109222             body = me.getEditorBody();
109223             html = body.innerHTML;
109224             if (Ext.isWebKit) {
109225                 bodyStyle = body.getAttribute('style'); // Safari puts text-align styles on the body element!
109226                 match = bodyStyle.match(/text-align:(.*?);/i);
109227                 if (match && match[1]) {
109228                     html = '<div style="' + match[0] + '">' + html + '</div>';
109229                 }
109230             }
109231             html = me.cleanHtml(html);
109232             if (me.fireEvent('beforesync', me, html) !== false) {
109233                 me.textareaEl.dom.value = html;
109234                 me.fireEvent('sync', me, html);
109235             }
109236         }
109237     },
109238
109239     //docs inherit from Field
109240     getValue : function() {
109241         var me = this,
109242             value;
109243         if (!me.sourceEditMode) {
109244             me.syncValue();
109245         }
109246         value = me.rendered ? me.textareaEl.dom.value : me.value;
109247         me.value = value;
109248         return value;
109249     },
109250
109251     /**
109252      * Pushes the value of the textarea into the iframe editor.
109253      * @protected
109254      */
109255     pushValue: function() {
109256         var me = this,
109257             v;
109258         if(me.initialized){
109259             v = me.textareaEl.dom.value || '';
109260             if (!me.activated && v.length < 1) {
109261                 v = me.defaultValue;
109262             }
109263             if (me.fireEvent('beforepush', me, v) !== false) {
109264                 me.getEditorBody().innerHTML = v;
109265                 if (Ext.isGecko) {
109266                     // Gecko hack, see: https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8
109267                     me.setDesignMode(false);  //toggle off first
109268                     me.setDesignMode(true);
109269                 }
109270                 me.fireEvent('push', me, v);
109271             }
109272         }
109273     },
109274
109275     // private
109276     deferFocus : function(){
109277          this.focus(false, true);
109278     },
109279
109280     getFocusEl: function() {
109281         var me = this,
109282             win = me.win;
109283         return win && !me.sourceEditMode ? win : me.textareaEl;
109284     },
109285
109286     // private
109287     initEditor : function(){
109288         //Destroying the component during/before initEditor can cause issues.
109289         try {
109290             var me = this,
109291                 dbody = me.getEditorBody(),
109292                 ss = me.textareaEl.getStyles('font-size', 'font-family', 'background-image', 'background-repeat', 'background-color', 'color'),
109293                 doc,
109294                 fn;
109295
109296             ss['background-attachment'] = 'fixed'; // w3c
109297             dbody.bgProperties = 'fixed'; // ie
109298
109299             Ext.DomHelper.applyStyles(dbody, ss);
109300
109301             doc = me.getDoc();
109302
109303             if (doc) {
109304                 try {
109305                     Ext.EventManager.removeAll(doc);
109306                 } catch(e) {}
109307             }
109308
109309             /*
109310              * We need to use createDelegate here, because when using buffer, the delayed task is added
109311              * as a property to the function. When the listener is removed, the task is deleted from the function.
109312              * Since onEditorEvent is shared on the prototype, if we have multiple html editors, the first time one of the editors
109313              * is destroyed, it causes the fn to be deleted from the prototype, which causes errors. Essentially, we're just anonymizing the function.
109314              */
109315             fn = Ext.Function.bind(me.onEditorEvent, me);
109316             Ext.EventManager.on(doc, {
109317                 mousedown: fn,
109318                 dblclick: fn,
109319                 click: fn,
109320                 keyup: fn,
109321                 buffer:100
109322             });
109323
109324             // These events need to be relayed from the inner document (where they stop
109325             // bubbling) up to the outer document. This has to be done at the DOM level so
109326             // the event reaches listeners on elements like the document body. The effected
109327             // mechanisms that depend on this bubbling behavior are listed to the right
109328             // of the event.
109329             fn = me.onRelayedEvent;
109330             Ext.EventManager.on(doc, {
109331                 mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront)
109332                 mousemove: fn, // window resize drag detection
109333                 mouseup: fn,   // window resize termination
109334                 click: fn,     // not sure, but just to be safe
109335                 dblclick: fn,  // not sure again
109336                 scope: me
109337             });
109338
109339             if (Ext.isGecko) {
109340                 Ext.EventManager.on(doc, 'keypress', me.applyCommand, me);
109341             }
109342             if (me.fixKeys) {
109343                 Ext.EventManager.on(doc, 'keydown', me.fixKeys, me);
109344             }
109345
109346             // We need to be sure we remove all our events from the iframe on unload or we're going to LEAK!
109347             Ext.EventManager.on(window, 'unload', me.beforeDestroy, me);
109348             doc.editorInitialized = true;
109349
109350             me.initialized = true;
109351             me.pushValue();
109352             me.setReadOnly(me.readOnly);
109353             me.fireEvent('initialize', me);
109354         } catch(ex) {
109355             // ignore (why?)
109356         }
109357     },
109358
109359     // private
109360     beforeDestroy : function(){
109361         var me = this,
109362             monitorTask = me.monitorTask,
109363             doc, prop;
109364
109365         if (monitorTask) {
109366             Ext.TaskManager.stop(monitorTask);
109367         }
109368         if (me.rendered) {
109369             try {
109370                 doc = me.getDoc();
109371                 if (doc) {
109372                     Ext.EventManager.removeAll(doc);
109373                     for (prop in doc) {
109374                         if (doc.hasOwnProperty(prop)) {
109375                             delete doc[prop];
109376                         }
109377                     }
109378                 }
109379             } catch(e) {
109380                 // ignore (why?)
109381             }
109382             Ext.destroyMembers(me, 'tb', 'toolbarWrap', 'iframeEl', 'textareaEl');
109383         }
109384         me.callParent();
109385     },
109386
109387     // private
109388     onRelayedEvent: function (event) {
109389         // relay event from the iframe's document to the document that owns the iframe...
109390
109391         var iframeEl = this.iframeEl,
109392             iframeXY = iframeEl.getXY(),
109393             eventXY = event.getXY();
109394
109395         // the event from the inner document has XY relative to that document's origin,
109396         // so adjust it to use the origin of the iframe in the outer document:
109397         event.xy = [iframeXY[0] + eventXY[0], iframeXY[1] + eventXY[1]];
109398
109399         event.injectEvent(iframeEl); // blame the iframe for the event...
109400
109401         event.xy = eventXY; // restore the original XY (just for safety)
109402     },
109403
109404     // private
109405     onFirstFocus : function(){
109406         var me = this,
109407             selection, range;
109408         me.activated = true;
109409         me.disableItems(me.readOnly);
109410         if (Ext.isGecko) { // prevent silly gecko errors
109411             me.win.focus();
109412             selection = me.win.getSelection();
109413             if (!selection.focusNode || selection.focusNode.nodeType !== 3) {
109414                 range = selection.getRangeAt(0);
109415                 range.selectNodeContents(me.getEditorBody());
109416                 range.collapse(true);
109417                 me.deferFocus();
109418             }
109419             try {
109420                 me.execCmd('useCSS', true);
109421                 me.execCmd('styleWithCSS', false);
109422             } catch(e) {
109423                 // ignore (why?)
109424             }
109425         }
109426         me.fireEvent('activate', me);
109427     },
109428
109429     // private
109430     adjustFont: function(btn) {
109431         var adjust = btn.getItemId() === 'increasefontsize' ? 1 : -1,
109432             size = this.getDoc().queryCommandValue('FontSize') || '2',
109433             isPxSize = Ext.isString(size) && size.indexOf('px') !== -1,
109434             isSafari;
109435         size = parseInt(size, 10);
109436         if (isPxSize) {
109437             // Safari 3 values
109438             // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px
109439             if (size <= 10) {
109440                 size = 1 + adjust;
109441             }
109442             else if (size <= 13) {
109443                 size = 2 + adjust;
109444             }
109445             else if (size <= 16) {
109446                 size = 3 + adjust;
109447             }
109448             else if (size <= 18) {
109449                 size = 4 + adjust;
109450             }
109451             else if (size <= 24) {
109452                 size = 5 + adjust;
109453             }
109454             else {
109455                 size = 6 + adjust;
109456             }
109457             size = Ext.Number.constrain(size, 1, 6);
109458         } else {
109459             isSafari = Ext.isSafari;
109460             if (isSafari) { // safari
109461                 adjust *= 2;
109462             }
109463             size = Math.max(1, size + adjust) + (isSafari ? 'px' : 0);
109464         }
109465         this.execCmd('FontSize', size);
109466     },
109467
109468     // private
109469     onEditorEvent: function(e) {
109470         this.updateToolbar();
109471     },
109472
109473     /**
109474      * Triggers a toolbar update by reading the markup state of the current selection in the editor.
109475      * @protected
109476      */
109477     updateToolbar: function() {
109478         var me = this,
109479             btns, doc, name, fontSelect;
109480
109481         if (me.readOnly) {
109482             return;
109483         }
109484
109485         if (!me.activated) {
109486             me.onFirstFocus();
109487             return;
109488         }
109489
109490         btns = me.getToolbar().items.map;
109491         doc = me.getDoc();
109492
109493         if (me.enableFont && !Ext.isSafari2) {
109494             name = (doc.queryCommandValue('FontName') || me.defaultFont).toLowerCase();
109495             fontSelect = me.fontSelect.dom;
109496             if (name !== fontSelect.value) {
109497                 fontSelect.value = name;
109498             }
109499         }
109500
109501         function updateButtons() {
109502             Ext.Array.forEach(Ext.Array.toArray(arguments), function(name) {
109503                 btns[name].toggle(doc.queryCommandState(name));
109504             });
109505         }
109506         if(me.enableFormat){
109507             updateButtons('bold', 'italic', 'underline');
109508         }
109509         if(me.enableAlignments){
109510             updateButtons('justifyleft', 'justifycenter', 'justifyright');
109511         }
109512         if(!Ext.isSafari2 && me.enableLists){
109513             updateButtons('insertorderedlist', 'insertunorderedlist');
109514         }
109515
109516         Ext.menu.Manager.hideAll();
109517
109518         me.syncValue();
109519     },
109520
109521     // private
109522     relayBtnCmd: function(btn) {
109523         this.relayCmd(btn.getItemId());
109524     },
109525
109526     /**
109527      * Executes a Midas editor command on the editor document and performs necessary focus and toolbar updates.
109528      * **This should only be called after the editor is initialized.**
109529      * @param {String} cmd The Midas command
109530      * @param {String/Boolean} [value=null] The value to pass to the command
109531      */
109532     relayCmd: function(cmd, value) {
109533         Ext.defer(function() {
109534             var me = this;
109535             me.focus();
109536             me.execCmd(cmd, value);
109537             me.updateToolbar();
109538         }, 10, this);
109539     },
109540
109541     /**
109542      * Executes a Midas editor command directly on the editor document. For visual commands, you should use
109543      * {@link #relayCmd} instead. **This should only be called after the editor is initialized.**
109544      * @param {String} cmd The Midas command
109545      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
109546      */
109547     execCmd : function(cmd, value){
109548         var me = this,
109549             doc = me.getDoc(),
109550             undef;
109551         doc.execCommand(cmd, false, value === undef ? null : value);
109552         me.syncValue();
109553     },
109554
109555     // private
109556     applyCommand : function(e){
109557         if (e.ctrlKey) {
109558             var me = this,
109559                 c = e.getCharCode(), cmd;
109560             if (c > 0) {
109561                 c = String.fromCharCode(c);
109562                 switch (c) {
109563                     case 'b':
109564                         cmd = 'bold';
109565                     break;
109566                     case 'i':
109567                         cmd = 'italic';
109568                     break;
109569                     case 'u':
109570                         cmd = 'underline';
109571                     break;
109572                 }
109573                 if (cmd) {
109574                     me.win.focus();
109575                     me.execCmd(cmd);
109576                     me.deferFocus();
109577                     e.preventDefault();
109578                 }
109579             }
109580         }
109581     },
109582
109583     /**
109584      * Inserts the passed text at the current cursor position.
109585      * Note: the editor must be initialized and activated to insert text.
109586      * @param {String} text
109587      */
109588     insertAtCursor : function(text){
109589         var me = this,
109590             range;
109591
109592         if (me.activated) {
109593             me.win.focus();
109594             if (Ext.isIE) {
109595                 range = me.getDoc().selection.createRange();
109596                 if (range) {
109597                     range.pasteHTML(text);
109598                     me.syncValue();
109599                     me.deferFocus();
109600                 }
109601             }else{
109602                 me.execCmd('InsertHTML', text);
109603                 me.deferFocus();
109604             }
109605         }
109606     },
109607
109608     // private
109609     fixKeys: function() { // load time branching for fastest keydown performance
109610         if (Ext.isIE) {
109611             return function(e){
109612                 var me = this,
109613                     k = e.getKey(),
109614                     doc = me.getDoc(),
109615                     range, target;
109616                 if (k === e.TAB) {
109617                     e.stopEvent();
109618                     range = doc.selection.createRange();
109619                     if(range){
109620                         range.collapse(true);
109621                         range.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
109622                         me.deferFocus();
109623                     }
109624                 }
109625                 else if (k === e.ENTER) {
109626                     range = doc.selection.createRange();
109627                     if (range) {
109628                         target = range.parentElement();
109629                         if(!target || target.tagName.toLowerCase() !== 'li'){
109630                             e.stopEvent();
109631                             range.pasteHTML('<br />');
109632                             range.collapse(false);
109633                             range.select();
109634                         }
109635                     }
109636                 }
109637             };
109638         }
109639
109640         if (Ext.isOpera) {
109641             return function(e){
109642                 var me = this;
109643                 if (e.getKey() === e.TAB) {
109644                     e.stopEvent();
109645                     me.win.focus();
109646                     me.execCmd('InsertHTML','&nbsp;&nbsp;&nbsp;&nbsp;');
109647                     me.deferFocus();
109648                 }
109649             };
109650         }
109651
109652         if (Ext.isWebKit) {
109653             return function(e){
109654                 var me = this,
109655                     k = e.getKey();
109656                 if (k === e.TAB) {
109657                     e.stopEvent();
109658                     me.execCmd('InsertText','\t');
109659                     me.deferFocus();
109660                 }
109661                 else if (k === e.ENTER) {
109662                     e.stopEvent();
109663                     me.execCmd('InsertHtml','<br /><br />');
109664                     me.deferFocus();
109665                 }
109666             };
109667         }
109668
109669         return null; // not needed, so null
109670     }(),
109671
109672     /**
109673      * Returns the editor's toolbar. **This is only available after the editor has been rendered.**
109674      * @return {Ext.toolbar.Toolbar}
109675      */
109676     getToolbar : function(){
109677         return this.toolbar;
109678     },
109679
109680     /**
109681      * @property {Object} buttonTips
109682      * Object collection of toolbar tooltips for the buttons in the editor. The key is the command id associated with
109683      * that button and the value is a valid QuickTips object. For example:
109684      *
109685      *     {
109686      *         bold : {
109687      *             title: 'Bold (Ctrl+B)',
109688      *             text: 'Make the selected text bold.',
109689      *             cls: 'x-html-editor-tip'
109690      *         },
109691      *         italic : {
109692      *             title: 'Italic (Ctrl+I)',
109693      *             text: 'Make the selected text italic.',
109694      *             cls: 'x-html-editor-tip'
109695      *         },
109696      *         ...
109697      */
109698     buttonTips : {
109699         bold : {
109700             title: 'Bold (Ctrl+B)',
109701             text: 'Make the selected text bold.',
109702             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109703         },
109704         italic : {
109705             title: 'Italic (Ctrl+I)',
109706             text: 'Make the selected text italic.',
109707             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109708         },
109709         underline : {
109710             title: 'Underline (Ctrl+U)',
109711             text: 'Underline the selected text.',
109712             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109713         },
109714         increasefontsize : {
109715             title: 'Grow Text',
109716             text: 'Increase the font size.',
109717             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109718         },
109719         decreasefontsize : {
109720             title: 'Shrink Text',
109721             text: 'Decrease the font size.',
109722             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109723         },
109724         backcolor : {
109725             title: 'Text Highlight Color',
109726             text: 'Change the background color of the selected text.',
109727             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109728         },
109729         forecolor : {
109730             title: 'Font Color',
109731             text: 'Change the color of the selected text.',
109732             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109733         },
109734         justifyleft : {
109735             title: 'Align Text Left',
109736             text: 'Align text to the left.',
109737             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109738         },
109739         justifycenter : {
109740             title: 'Center Text',
109741             text: 'Center text in the editor.',
109742             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109743         },
109744         justifyright : {
109745             title: 'Align Text Right',
109746             text: 'Align text to the right.',
109747             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109748         },
109749         insertunorderedlist : {
109750             title: 'Bullet List',
109751             text: 'Start a bulleted list.',
109752             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109753         },
109754         insertorderedlist : {
109755             title: 'Numbered List',
109756             text: 'Start a numbered list.',
109757             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109758         },
109759         createlink : {
109760             title: 'Hyperlink',
109761             text: 'Make the selected text a hyperlink.',
109762             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109763         },
109764         sourceedit : {
109765             title: 'Source Edit',
109766             text: 'Switch to source editing mode.',
109767             cls: Ext.baseCSSPrefix + 'html-editor-tip'
109768         }
109769     }
109770
109771     // hide stuff that is not compatible
109772     /**
109773      * @event blur
109774      * @hide
109775      */
109776     /**
109777      * @event change
109778      * @hide
109779      */
109780     /**
109781      * @event focus
109782      * @hide
109783      */
109784     /**
109785      * @event specialkey
109786      * @hide
109787      */
109788     /**
109789      * @cfg {String} fieldCls @hide
109790      */
109791     /**
109792      * @cfg {String} focusCls @hide
109793      */
109794     /**
109795      * @cfg {String} autoCreate @hide
109796      */
109797     /**
109798      * @cfg {String} inputType @hide
109799      */
109800     /**
109801      * @cfg {String} invalidCls @hide
109802      */
109803     /**
109804      * @cfg {String} invalidText @hide
109805      */
109806     /**
109807      * @cfg {String} msgFx @hide
109808      */
109809     /**
109810      * @cfg {Boolean} allowDomMove @hide
109811      */
109812     /**
109813      * @cfg {String} applyTo @hide
109814      */
109815     /**
109816      * @cfg {String} readOnly  @hide
109817      */
109818     /**
109819      * @cfg {String} tabIndex  @hide
109820      */
109821     /**
109822      * @method validate
109823      * @hide
109824      */
109825 });
109826
109827 /**
109828  * @docauthor Robert Dougan <rob@sencha.com>
109829  *
109830  * Single radio field. Similar to checkbox, but automatically handles making sure only one radio is checked
109831  * at a time within a group of radios with the same name.
109832  *
109833  * # Labeling
109834  *
109835  * In addition to the {@link Ext.form.Labelable standard field labeling options}, radio buttons
109836  * may be given an optional {@link #boxLabel} which will be displayed immediately to the right of the input. Also
109837  * see {@link Ext.form.RadioGroup} for a convenient method of grouping related radio buttons.
109838  *
109839  * # Values
109840  *
109841  * The main value of a Radio field is a boolean, indicating whether or not the radio is checked.
109842  *
109843  * The following values will check the radio:
109844  *
109845  * - `true`
109846  * - `'true'`
109847  * - `'1'`
109848  * - `'on'`
109849  *
109850  * Any other value will uncheck it.
109851  *
109852  * In addition to the main boolean value, you may also specify a separate {@link #inputValue}. This will be sent
109853  * as the parameter value when the form is {@link Ext.form.Basic#submit submitted}. You will want to set this
109854  * value if you have multiple radio buttons with the same {@link #name}, as is almost always the case.
109855  *
109856  * # Example usage
109857  *
109858  *     @example
109859  *     Ext.create('Ext.form.Panel', {
109860  *         title      : 'Order Form',
109861  *         width      : 300,
109862  *         bodyPadding: 10,
109863  *         renderTo   : Ext.getBody(),
109864  *         items: [
109865  *             {
109866  *                 xtype      : 'fieldcontainer',
109867  *                 fieldLabel : 'Size',
109868  *                 defaultType: 'radiofield',
109869  *                 defaults: {
109870  *                     flex: 1
109871  *                 },
109872  *                 layout: 'hbox',
109873  *                 items: [
109874  *                     {
109875  *                         boxLabel  : 'M',
109876  *                         name      : 'size',
109877  *                         inputValue: 'm',
109878  *                         id        : 'radio1'
109879  *                     }, {
109880  *                         boxLabel  : 'L',
109881  *                         name      : 'size',
109882  *                         inputValue: 'l',
109883  *                         id        : 'radio2'
109884  *                     }, {
109885  *                         boxLabel  : 'XL',
109886  *                         name      : 'size',
109887  *                         inputValue: 'xl',
109888  *                         id        : 'radio3'
109889  *                     }
109890  *                 ]
109891  *             },
109892  *             {
109893  *                 xtype      : 'fieldcontainer',
109894  *                 fieldLabel : 'Color',
109895  *                 defaultType: 'radiofield',
109896  *                 defaults: {
109897  *                     flex: 1
109898  *                 },
109899  *                 layout: 'hbox',
109900  *                 items: [
109901  *                     {
109902  *                         boxLabel  : 'Blue',
109903  *                         name      : 'color',
109904  *                         inputValue: 'blue',
109905  *                         id        : 'radio4'
109906  *                     }, {
109907  *                         boxLabel  : 'Grey',
109908  *                         name      : 'color',
109909  *                         inputValue: 'grey',
109910  *                         id        : 'radio5'
109911  *                     }, {
109912  *                         boxLabel  : 'Black',
109913  *                         name      : 'color',
109914  *                         inputValue: 'black',
109915  *                         id        : 'radio6'
109916  *                     }
109917  *                 ]
109918  *             }
109919  *         ],
109920  *         bbar: [
109921  *             {
109922  *                 text: 'Smaller Size',
109923  *                 handler: function() {
109924  *                     var radio1 = Ext.getCmp('radio1'),
109925  *                         radio2 = Ext.getCmp('radio2'),
109926  *                         radio3 = Ext.getCmp('radio3');
109927  *
109928  *                     //if L is selected, change to M
109929  *                     if (radio2.getValue()) {
109930  *                         radio1.setValue(true);
109931  *                         return;
109932  *                     }
109933  *
109934  *                     //if XL is selected, change to L
109935  *                     if (radio3.getValue()) {
109936  *                         radio2.setValue(true);
109937  *                         return;
109938  *                     }
109939  *
109940  *                     //if nothing is set, set size to S
109941  *                     radio1.setValue(true);
109942  *                 }
109943  *             },
109944  *             {
109945  *                 text: 'Larger Size',
109946  *                 handler: function() {
109947  *                     var radio1 = Ext.getCmp('radio1'),
109948  *                         radio2 = Ext.getCmp('radio2'),
109949  *                         radio3 = Ext.getCmp('radio3');
109950  *
109951  *                     //if M is selected, change to L
109952  *                     if (radio1.getValue()) {
109953  *                         radio2.setValue(true);
109954  *                         return;
109955  *                     }
109956  *
109957  *                     //if L is selected, change to XL
109958  *                     if (radio2.getValue()) {
109959  *                         radio3.setValue(true);
109960  *                         return;
109961  *                     }
109962  *
109963  *                     //if nothing is set, set size to XL
109964  *                     radio3.setValue(true);
109965  *                 }
109966  *             },
109967  *             '-',
109968  *             {
109969  *                 text: 'Select color',
109970  *                 menu: {
109971  *                     indent: false,
109972  *                     items: [
109973  *                         {
109974  *                             text: 'Blue',
109975  *                             handler: function() {
109976  *                                 var radio = Ext.getCmp('radio4');
109977  *                                 radio.setValue(true);
109978  *                             }
109979  *                         },
109980  *                         {
109981  *                             text: 'Grey',
109982  *                             handler: function() {
109983  *                                 var radio = Ext.getCmp('radio5');
109984  *                                 radio.setValue(true);
109985  *                             }
109986  *                         },
109987  *                         {
109988  *                             text: 'Black',
109989  *                             handler: function() {
109990  *                                 var radio = Ext.getCmp('radio6');
109991  *                                 radio.setValue(true);
109992  *                             }
109993  *                         }
109994  *                     ]
109995  *                 }
109996  *             }
109997  *         ]
109998  *     });
109999  */
110000 Ext.define('Ext.form.field.Radio', {
110001     extend:'Ext.form.field.Checkbox',
110002     alias: ['widget.radiofield', 'widget.radio'],
110003     alternateClassName: 'Ext.form.Radio',
110004     requires: ['Ext.form.RadioManager'],
110005
110006     isRadio: true,
110007
110008     /**
110009      * @cfg {String} uncheckedValue @hide
110010      */
110011
110012     // private
110013     inputType: 'radio',
110014     ariaRole: 'radio',
110015
110016     /**
110017      * If this radio is part of a group, it will return the selected value
110018      * @return {String}
110019      */
110020     getGroupValue: function() {
110021         var selected = this.getManager().getChecked(this.name);
110022         return selected ? selected.inputValue : null;
110023     },
110024
110025     /**
110026      * @private Handle click on the radio button
110027      */
110028     onBoxClick: function(e) {
110029         var me = this;
110030         if (!me.disabled && !me.readOnly) {
110031             this.setValue(true);
110032         }
110033     },
110034
110035     /**
110036      * Sets either the checked/unchecked status of this Radio, or, if a string value is passed, checks a sibling Radio
110037      * of the same name whose value is the value specified.
110038      * @param {String/Boolean} value Checked value, or the value of the sibling radio button to check.
110039      * @return {Ext.form.field.Radio} this
110040      */
110041     setValue: function(v) {
110042         var me = this,
110043             active;
110044
110045         if (Ext.isBoolean(v)) {
110046             me.callParent(arguments);
110047         } else {
110048             active = me.getManager().getWithValue(me.name, v).getAt(0);
110049             if (active) {
110050                 active.setValue(true);
110051             }
110052         }
110053         return me;
110054     },
110055
110056     /**
110057      * Returns the submit value for the checkbox which can be used when submitting forms.
110058      * @return {Boolean/Object} True if checked, null if not.
110059      */
110060     getSubmitValue: function() {
110061         return this.checked ? this.inputValue : null;
110062     },
110063
110064     getModelData: function() {
110065         return this.getSubmitData();
110066     },
110067
110068     // inherit docs
110069     onChange: function(newVal, oldVal) {
110070         var me = this;
110071         me.callParent(arguments);
110072
110073         if (newVal) {
110074             this.getManager().getByName(me.name).each(function(item){
110075                 if (item !== me) {
110076                     item.setValue(false);
110077                 }
110078             }, me);
110079         }
110080     },
110081
110082     // inherit docs
110083     getManager: function() {
110084         return Ext.form.RadioManager;
110085     }
110086 });
110087
110088 /**
110089  * A time picker which provides a list of times from which to choose. This is used by the Ext.form.field.Time
110090  * class to allow browsing and selection of valid times, but could also be used with other components.
110091  *
110092  * By default, all times starting at midnight and incrementing every 15 minutes will be presented. This list of
110093  * available times can be controlled using the {@link #minValue}, {@link #maxValue}, and {@link #increment}
110094  * configuration properties. The format of the times presented in the list can be customized with the {@link #format}
110095  * config.
110096  *
110097  * To handle when the user selects a time from the list, you can subscribe to the {@link #selectionchange} event.
110098  *
110099  *     @example
110100  *     Ext.create('Ext.picker.Time', {
110101  *        width: 60,
110102  *        minValue: Ext.Date.parse('04:30:00 AM', 'h:i:s A'),
110103  *        maxValue: Ext.Date.parse('08:00:00 AM', 'h:i:s A'),
110104  *        renderTo: Ext.getBody()
110105  *     });
110106  */
110107 Ext.define('Ext.picker.Time', {
110108     extend: 'Ext.view.BoundList',
110109     alias: 'widget.timepicker',
110110     requires: ['Ext.data.Store', 'Ext.Date'],
110111
110112     /**
110113      * @cfg {Date} minValue
110114      * The minimum time to be shown in the list of times. This must be a Date object (only the time fields will be
110115      * used); no parsing of String values will be done.
110116      */
110117
110118     /**
110119      * @cfg {Date} maxValue
110120      * The maximum time to be shown in the list of times. This must be a Date object (only the time fields will be
110121      * used); no parsing of String values will be done.
110122      */
110123
110124     /**
110125      * @cfg {Number} increment
110126      * The number of minutes between each time value in the list.
110127      */
110128     increment: 15,
110129
110130     /**
110131      * @cfg {String} format
110132      * The default time format string which can be overriden for localization support. The format must be valid
110133      * according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time format try 'H:i'
110134      * instead.
110135      */
110136     format : "g:i A",
110137
110138     /**
110139      * @hide
110140      * The field in the implicitly-generated Model objects that gets displayed in the list. This is
110141      * an internal field name only and is not useful to change via config.
110142      */
110143     displayField: 'disp',
110144
110145     /**
110146      * @private
110147      * Year, month, and day that all times will be normalized into internally.
110148      */
110149     initDate: [2008,0,1],
110150
110151     componentCls: Ext.baseCSSPrefix + 'timepicker',
110152
110153     /**
110154      * @hide
110155      */
110156     loadMask: false,
110157
110158     initComponent: function() {
110159         var me = this,
110160             dateUtil = Ext.Date,
110161             clearTime = dateUtil.clearTime,
110162             initDate = me.initDate;
110163
110164         // Set up absolute min and max for the entire day
110165         me.absMin = clearTime(new Date(initDate[0], initDate[1], initDate[2]));
110166         me.absMax = dateUtil.add(clearTime(new Date(initDate[0], initDate[1], initDate[2])), 'mi', (24 * 60) - 1);
110167
110168         me.store = me.createStore();
110169         me.updateList();
110170
110171         me.callParent();
110172     },
110173
110174     /**
110175      * Set the {@link #minValue} and update the list of available times. This must be a Date object (only the time
110176      * fields will be used); no parsing of String values will be done.
110177      * @param {Date} value
110178      */
110179     setMinValue: function(value) {
110180         this.minValue = value;
110181         this.updateList();
110182     },
110183
110184     /**
110185      * Set the {@link #maxValue} and update the list of available times. This must be a Date object (only the time
110186      * fields will be used); no parsing of String values will be done.
110187      * @param {Date} value
110188      */
110189     setMaxValue: function(value) {
110190         this.maxValue = value;
110191         this.updateList();
110192     },
110193
110194     /**
110195      * @private
110196      * Sets the year/month/day of the given Date object to the {@link #initDate}, so that only
110197      * the time fields are significant. This makes values suitable for time comparison.
110198      * @param {Date} date
110199      */
110200     normalizeDate: function(date) {
110201         var initDate = this.initDate;
110202         date.setFullYear(initDate[0], initDate[1], initDate[2]);
110203         return date;
110204     },
110205
110206     /**
110207      * Update the list of available times in the list to be constrained within the {@link #minValue}
110208      * and {@link #maxValue}.
110209      */
110210     updateList: function() {
110211         var me = this,
110212             min = me.normalizeDate(me.minValue || me.absMin),
110213             max = me.normalizeDate(me.maxValue || me.absMax);
110214
110215         me.store.filterBy(function(record) {
110216             var date = record.get('date');
110217             return date >= min && date <= max;
110218         });
110219     },
110220
110221     /**
110222      * @private
110223      * Creates the internal {@link Ext.data.Store} that contains the available times. The store
110224      * is loaded with all possible times, and it is later filtered to hide those times outside
110225      * the minValue/maxValue.
110226      */
110227     createStore: function() {
110228         var me = this,
110229             utilDate = Ext.Date,
110230             times = [],
110231             min = me.absMin,
110232             max = me.absMax;
110233
110234         while(min <= max){
110235             times.push({
110236                 disp: utilDate.dateFormat(min, me.format),
110237                 date: min
110238             });
110239             min = utilDate.add(min, 'mi', me.increment);
110240         }
110241
110242         return Ext.create('Ext.data.Store', {
110243             fields: ['disp', 'date'],
110244             data: times
110245         });
110246     }
110247
110248 });
110249
110250 /**
110251  * Provides a time input field with a time dropdown and automatic time validation.
110252  *
110253  * This field recognizes and uses JavaScript Date objects as its main {@link #value} type (only the time portion of the
110254  * date is used; the month/day/year are ignored). In addition, it recognizes string values which are parsed according to
110255  * the {@link #format} and/or {@link #altFormats} configs. These may be reconfigured to use time formats appropriate for
110256  * the user's locale.
110257  *
110258  * The field may be limited to a certain range of times by using the {@link #minValue} and {@link #maxValue} configs,
110259  * and the interval between time options in the dropdown can be changed with the {@link #increment} config.
110260  *
110261  * Example usage:
110262  *
110263  *     @example
110264  *     Ext.create('Ext.form.Panel', {
110265  *         title: 'Time Card',
110266  *         width: 300,
110267  *         bodyPadding: 10,
110268  *         renderTo: Ext.getBody(),
110269  *         items: [{
110270  *             xtype: 'timefield',
110271  *             name: 'in',
110272  *             fieldLabel: 'Time In',
110273  *             minValue: '6:00 AM',
110274  *             maxValue: '8:00 PM',
110275  *             increment: 30,
110276  *             anchor: '100%'
110277  *         }, {
110278  *             xtype: 'timefield',
110279  *             name: 'out',
110280  *             fieldLabel: 'Time Out',
110281  *             minValue: '6:00 AM',
110282  *             maxValue: '8:00 PM',
110283  *             increment: 30,
110284  *             anchor: '100%'
110285  *        }]
110286  *     });
110287  */
110288 Ext.define('Ext.form.field.Time', {
110289     extend:'Ext.form.field.Picker',
110290     alias: 'widget.timefield',
110291     requires: ['Ext.form.field.Date', 'Ext.picker.Time', 'Ext.view.BoundListKeyNav', 'Ext.Date'],
110292     alternateClassName: ['Ext.form.TimeField', 'Ext.form.Time'],
110293
110294     /**
110295      * @cfg {String} triggerCls
110296      * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
110297      * by default and triggerCls will be **appended** if specified. Defaults to 'x-form-time-trigger' for the Time field
110298      * trigger.
110299      */
110300     triggerCls: Ext.baseCSSPrefix + 'form-time-trigger',
110301
110302     /**
110303      * @cfg {Date/String} minValue
110304      * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
110305      * valid format -- see {@link #format} and {@link #altFormats}.
110306      */
110307
110308     /**
110309      * @cfg {Date/String} maxValue
110310      * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
110311      * valid format -- see {@link #format} and {@link #altFormats}.
110312      */
110313
110314     /**
110315      * @cfg {String} minText
110316      * The error text to display when the entered time is before {@link #minValue}.
110317      */
110318     minText : "The time in this field must be equal to or after {0}",
110319
110320     /**
110321      * @cfg {String} maxText
110322      * The error text to display when the entered time is after {@link #maxValue}.
110323      */
110324     maxText : "The time in this field must be equal to or before {0}",
110325
110326     /**
110327      * @cfg {String} invalidText
110328      * The error text to display when the time in the field is invalid.
110329      */
110330     invalidText : "{0} is not a valid time",
110331
110332     /**
110333      * @cfg {String} format
110334      * The default time format string which can be overriden for localization support. The format must be valid
110335      * according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time format try 'H:i'
110336      * instead.
110337      */
110338     format : "g:i A",
110339
110340     /**
110341      * @cfg {String} submitFormat
110342      * The date format string which will be submitted to the server. The format must be valid according to {@link
110343      * Ext.Date#parse} (defaults to {@link #format}).
110344      */
110345
110346     /**
110347      * @cfg {String} altFormats
110348      * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
110349      * format.
110350      */
110351     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",
110352
110353     /**
110354      * @cfg {Number} increment
110355      * The number of minutes between each time value in the list.
110356      */
110357     increment: 15,
110358
110359     /**
110360      * @cfg {Number} pickerMaxHeight
110361      * The maximum height of the {@link Ext.picker.Time} dropdown.
110362      */
110363     pickerMaxHeight: 300,
110364
110365     /**
110366      * @cfg {Boolean} selectOnTab
110367      * Whether the Tab key should select the currently highlighted item.
110368      */
110369     selectOnTab: true,
110370
110371     /**
110372      * @private
110373      * This is the date to use when generating time values in the absence of either minValue
110374      * or maxValue.  Using the current date causes DST issues on DST boundary dates, so this is an
110375      * arbitrary "safe" date that can be any date aside from DST boundary dates.
110376      */
110377     initDate: '1/1/2008',
110378     initDateFormat: 'j/n/Y',
110379
110380
110381     initComponent: function() {
110382         var me = this,
110383             min = me.minValue,
110384             max = me.maxValue;
110385         if (min) {
110386             me.setMinValue(min);
110387         }
110388         if (max) {
110389             me.setMaxValue(max);
110390         }
110391         this.callParent();
110392     },
110393
110394     initValue: function() {
110395         var me = this,
110396             value = me.value;
110397
110398         // If a String value was supplied, try to convert it to a proper Date object
110399         if (Ext.isString(value)) {
110400             me.value = me.rawToValue(value);
110401         }
110402
110403         me.callParent();
110404     },
110405
110406     /**
110407      * Replaces any existing {@link #minValue} with the new time and refreshes the picker's range.
110408      * @param {Date/String} value The minimum time that can be selected
110409      */
110410     setMinValue: function(value) {
110411         var me = this,
110412             picker = me.picker;
110413         me.setLimit(value, true);
110414         if (picker) {
110415             picker.setMinValue(me.minValue);
110416         }
110417     },
110418
110419     /**
110420      * Replaces any existing {@link #maxValue} with the new time and refreshes the picker's range.
110421      * @param {Date/String} value The maximum time that can be selected
110422      */
110423     setMaxValue: function(value) {
110424         var me = this,
110425             picker = me.picker;
110426         me.setLimit(value, false);
110427         if (picker) {
110428             picker.setMaxValue(me.maxValue);
110429         }
110430     },
110431
110432     /**
110433      * @private
110434      * Updates either the min or max value. Converts the user's value into a Date object whose
110435      * year/month/day is set to the {@link #initDate} so that only the time fields are significant.
110436      */
110437     setLimit: function(value, isMin) {
110438         var me = this,
110439             d, val;
110440         if (Ext.isString(value)) {
110441             d = me.parseDate(value);
110442         }
110443         else if (Ext.isDate(value)) {
110444             d = value;
110445         }
110446         if (d) {
110447             val = Ext.Date.clearTime(new Date(me.initDate));
110448             val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
110449             me[isMin ? 'minValue' : 'maxValue'] = val;
110450         }
110451     },
110452
110453     rawToValue: function(rawValue) {
110454         return this.parseDate(rawValue) || rawValue || null;
110455     },
110456
110457     valueToRaw: function(value) {
110458         return this.formatDate(this.parseDate(value));
110459     },
110460
110461     /**
110462      * Runs all of Time's validations and returns an array of any errors. Note that this first runs Text's validations,
110463      * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
110464      * the time format is valid, that the chosen time is within the {@link #minValue} and {@link #maxValue} constraints
110465      * set.
110466      * @param {Object} [value] The value to get errors for (defaults to the current field value)
110467      * @return {String[]} All validation errors for this field
110468      */
110469     getErrors: function(value) {
110470         var me = this,
110471             format = Ext.String.format,
110472             errors = me.callParent(arguments),
110473             minValue = me.minValue,
110474             maxValue = me.maxValue,
110475             date;
110476
110477         value = me.formatDate(value || me.processRawValue(me.getRawValue()));
110478
110479         if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
110480              return errors;
110481         }
110482
110483         date = me.parseDate(value);
110484         if (!date) {
110485             errors.push(format(me.invalidText, value, me.format));
110486             return errors;
110487         }
110488
110489         if (minValue && date < minValue) {
110490             errors.push(format(me.minText, me.formatDate(minValue)));
110491         }
110492
110493         if (maxValue && date > maxValue) {
110494             errors.push(format(me.maxText, me.formatDate(maxValue)));
110495         }
110496
110497         return errors;
110498     },
110499
110500     formatDate: function() {
110501         return Ext.form.field.Date.prototype.formatDate.apply(this, arguments);
110502     },
110503
110504     /**
110505      * @private
110506      * Parses an input value into a valid Date object.
110507      * @param {String/Date} value
110508      */
110509     parseDate: function(value) {
110510         if (!value || Ext.isDate(value)) {
110511             return value;
110512         }
110513
110514         var me = this,
110515             val = me.safeParse(value, me.format),
110516             altFormats = me.altFormats,
110517             altFormatsArray = me.altFormatsArray,
110518             i = 0,
110519             len;
110520
110521         if (!val && altFormats) {
110522             altFormatsArray = altFormatsArray || altFormats.split('|');
110523             len = altFormatsArray.length;
110524             for (; i < len && !val; ++i) {
110525                 val = me.safeParse(value, altFormatsArray[i]);
110526             }
110527         }
110528         return val;
110529     },
110530
110531     safeParse: function(value, format){
110532         var me = this,
110533             utilDate = Ext.Date,
110534             parsedDate,
110535             result = null;
110536
110537         if (utilDate.formatContainsDateInfo(format)) {
110538             // assume we've been given a full date
110539             result = utilDate.parse(value, format);
110540         } else {
110541             // Use our initial safe date
110542             parsedDate = utilDate.parse(me.initDate + ' ' + value, me.initDateFormat + ' ' + format);
110543             if (parsedDate) {
110544                 result = parsedDate;
110545             }
110546         }
110547         return result;
110548     },
110549
110550     // @private
110551     getSubmitValue: function() {
110552         var me = this,
110553             format = me.submitFormat || me.format,
110554             value = me.getValue();
110555
110556         return value ? Ext.Date.format(value, format) : null;
110557     },
110558
110559     /**
110560      * @private
110561      * Creates the {@link Ext.picker.Time}
110562      */
110563     createPicker: function() {
110564         var me = this,
110565             picker = Ext.create('Ext.picker.Time', {
110566                 pickerField: me,
110567                 selModel: {
110568                     mode: 'SINGLE'
110569                 },
110570                 floating: true,
110571                 hidden: true,
110572                 minValue: me.minValue,
110573                 maxValue: me.maxValue,
110574                 increment: me.increment,
110575                 format: me.format,
110576                 ownerCt: this.ownerCt,
110577                 renderTo: document.body,
110578                 maxHeight: me.pickerMaxHeight,
110579                 focusOnToFront: false
110580             });
110581
110582         me.mon(picker.getSelectionModel(), {
110583             selectionchange: me.onListSelect,
110584             scope: me
110585         });
110586
110587         return picker;
110588     },
110589
110590     /**
110591      * @private
110592      * Enables the key nav for the Time picker when it is expanded.
110593      * TODO this is largely the same logic as ComboBox, should factor out.
110594      */
110595     onExpand: function() {
110596         var me = this,
110597             keyNav = me.pickerKeyNav,
110598             selectOnTab = me.selectOnTab,
110599             picker = me.getPicker(),
110600             lastSelected = picker.getSelectionModel().lastSelected,
110601             itemNode;
110602
110603         if (!keyNav) {
110604             keyNav = me.pickerKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, {
110605                 boundList: picker,
110606                 forceKeyDown: true,
110607                 tab: function(e) {
110608                     if (selectOnTab) {
110609                         if(me.picker.highlightedItem) {
110610                             this.selectHighlighted(e);
110611                         } else {
110612                             me.collapse();
110613                         }
110614                         me.triggerBlur();
110615                     }
110616                     // Tab key event is allowed to propagate to field
110617                     return true;
110618                 }
110619             });
110620             // stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab
110621             if (selectOnTab) {
110622                 me.ignoreMonitorTab = true;
110623             }
110624         }
110625         Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker
110626
110627         // Highlight the last selected item and scroll it into view
110628         if (lastSelected) {
110629             itemNode = picker.getNode(lastSelected);
110630             if (itemNode) {
110631                 picker.highlightItem(itemNode);
110632                 picker.el.scrollChildIntoView(itemNode, false);
110633             }
110634         }
110635     },
110636
110637     /**
110638      * @private
110639      * Disables the key nav for the Time picker when it is collapsed.
110640      */
110641     onCollapse: function() {
110642         var me = this,
110643             keyNav = me.pickerKeyNav;
110644         if (keyNav) {
110645             keyNav.disable();
110646             me.ignoreMonitorTab = false;
110647         }
110648     },
110649
110650     /**
110651      * @private
110652      * Clears the highlighted item in the picker on change.
110653      * This prevents the highlighted item from being selected instead of the custom typed in value when the tab key is pressed.
110654      */
110655     onChange: function() {
110656         var me = this,
110657             picker = me.picker;
110658
110659         me.callParent(arguments);
110660         if(picker) {
110661             picker.clearHighlight();
110662         }
110663     },
110664
110665     /**
110666      * @private
110667      * Handles a time being selected from the Time picker.
110668      */
110669     onListSelect: function(list, recordArray) {
110670         var me = this,
110671             record = recordArray[0],
110672             val = record ? record.get('date') : null;
110673         me.setValue(val);
110674         me.fireEvent('select', me, val);
110675         me.picker.clearHighlight();
110676         me.collapse();
110677         me.inputEl.focus();
110678     }
110679 });
110680
110681
110682 /**
110683  * @class Ext.grid.CellEditor
110684  * @extends Ext.Editor
110685  * Internal utility class that provides default configuration for cell editing.
110686  * @ignore
110687  */
110688 Ext.define('Ext.grid.CellEditor', {
110689     extend: 'Ext.Editor',
110690     constructor: function(config) {
110691         config = Ext.apply({}, config);
110692         
110693         if (config.field) {
110694             config.field.monitorTab = false;
110695         }
110696         if (!Ext.isDefined(config.autoSize)) {
110697             config.autoSize = {
110698                 width: 'boundEl'
110699             };
110700         }
110701         this.callParent([config]);
110702     },
110703     
110704     /**
110705      * @private
110706      * Hide the grid cell when editor is shown.
110707      */
110708     onShow: function() {
110709         var first = this.boundEl.first();
110710         if (first) {
110711             first.hide();
110712         }
110713         this.callParent(arguments);
110714     },
110715     
110716     /**
110717      * @private
110718      * Show grid cell when editor is hidden.
110719      */
110720     onHide: function() {
110721         var first = this.boundEl.first();
110722         if (first) {
110723             first.show();
110724         }
110725         this.callParent(arguments);
110726     },
110727     
110728     /**
110729      * @private
110730      * Fix checkbox blur when it is clicked.
110731      */
110732     afterRender: function() {
110733         this.callParent(arguments);
110734         var field = this.field;
110735         if (field.isXType('checkboxfield')) {
110736             field.mon(field.inputEl, 'mousedown', this.onCheckBoxMouseDown, this);
110737             field.mon(field.inputEl, 'click', this.onCheckBoxClick, this);
110738         }
110739     },
110740     
110741     /**
110742      * @private
110743      * Because when checkbox is clicked it loses focus  completeEdit is bypassed.
110744      */
110745     onCheckBoxMouseDown: function() {
110746         this.completeEdit = Ext.emptyFn;
110747     },
110748     
110749     /**
110750      * @private
110751      * Restore checkbox focus and completeEdit method.
110752      */
110753     onCheckBoxClick: function() {
110754         delete this.completeEdit;
110755         this.field.focus(false, 10);
110756     },
110757     
110758     alignment: "tl-tl",
110759     hideEl : false,
110760     cls: Ext.baseCSSPrefix + "small-editor " + Ext.baseCSSPrefix + "grid-editor",
110761     shim: false,
110762     shadow: false
110763 });
110764 /**
110765  * @class Ext.grid.ColumnLayout
110766  * @extends Ext.layout.container.HBox
110767  * @private
110768  *
110769  * <p>This class is used only by the grid's HeaderContainer docked child.</p>
110770  *
110771  * <p>It adds the ability to shrink the vertical size of the inner container element back if a grouped
110772  * column header has all its child columns dragged out, and the whole HeaderContainer needs to shrink back down.</p>
110773  *
110774  * <p>Also, after every layout, after all headers have attained their 'stretchmax' height, it goes through and calls
110775  * <code>setPadding</code> on the columns so that they lay out correctly.</p>
110776  */
110777 Ext.define('Ext.grid.ColumnLayout', {
110778     extend: 'Ext.layout.container.HBox',
110779     alias: 'layout.gridcolumn',
110780     type : 'column',
110781
110782     reserveOffset: false,
110783
110784     shrinkToFit: false,
110785
110786     // Height-stretched innerCt must be able to revert back to unstretched height
110787     clearInnerCtOnLayout: true,
110788
110789     beforeLayout: function() {
110790         var me = this,
110791             i = 0,
110792             items = me.getLayoutItems(),
110793             len = items.length,
110794             item, returnValue,
110795             s;
110796
110797         // Scrollbar offset defined by width of any vertical scroller in the owning grid
110798         if (!Ext.isDefined(me.availableSpaceOffset)) {
110799             s = me.owner.up('tablepanel').verticalScroller;
110800             me.availableSpaceOffset = s ? s.width-1 : 0;
110801         }
110802
110803         returnValue = me.callParent(arguments);
110804
110805         // Size to a sane minimum height before possibly being stretched to accommodate grouped headers
110806         me.innerCt.setHeight(23);
110807
110808         // Unstretch child items before the layout which stretches them.
110809         for (; i < len; i++) {
110810             item = items[i];
110811             item.el.setStyle({
110812                 height: 'auto'
110813             });
110814             item.titleContainer.setStyle({
110815                 height: 'auto',
110816                 paddingTop: '0'
110817             });
110818             if (item.componentLayout && item.componentLayout.lastComponentSize) {
110819                 item.componentLayout.lastComponentSize.height = item.el.dom.offsetHeight;
110820             }
110821         }
110822         return returnValue;
110823     },
110824
110825     // Override to enforce the forceFit config.
110826     calculateChildBoxes: function(visibleItems, targetSize) {
110827         var me = this,
110828             calculations = me.callParent(arguments),
110829             boxes = calculations.boxes,
110830             metaData = calculations.meta,
110831             len = boxes.length, i = 0, box, item;
110832
110833         if (targetSize.width && !me.isHeader) {
110834             // If configured forceFit then all columns will be flexed
110835             if (me.owner.forceFit) {
110836
110837                 for (; i < len; i++) {
110838                     box = boxes[i];
110839                     item = box.component;
110840
110841                     // Set a sane minWidth for the Box layout to be able to squeeze flexed Headers down to.
110842                     item.minWidth = Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
110843
110844                     // For forceFit, just use allocated width as the flex value, and the proportions
110845                     // will end up the same whatever HeaderContainer width they are being forced into.
110846                     item.flex = box.width;
110847                 }
110848
110849                 // Recalculate based upon all columns now being flexed instead of sized.
110850                 calculations = me.callParent(arguments);
110851             }
110852             else if (metaData.tooNarrow) {
110853                 targetSize.width = metaData.desiredSize;
110854             }
110855         }
110856
110857         return calculations;
110858     },
110859
110860     afterLayout: function() {
110861         var me = this,
110862             owner = me.owner,
110863             topGrid,
110864             bothHeaderCts,
110865             otherHeaderCt,
110866             thisHeight,
110867             otherHeight,
110868             modifiedGrid,
110869             i = 0,
110870             items,
110871             len,
110872             headerHeight;
110873
110874         me.callParent(arguments);
110875
110876         // Set up padding in items
110877         if (!me.owner.hideHeaders) {
110878
110879             // If this is one HeaderContainer of a pair in a side-by-side locking view, then find the height
110880             // of the highest one, and sync the other one to that height.
110881             if (owner.lockableInjected) {
110882                 topGrid = owner.up('tablepanel').up('tablepanel');
110883                 bothHeaderCts = topGrid.query('headercontainer:not([isHeader])');
110884                 otherHeaderCt = (bothHeaderCts[0] === owner) ? bothHeaderCts[1] : bothHeaderCts[0];
110885
110886                 // Both sides must be rendered for this syncing operation to work.
110887                 if (!otherHeaderCt.rendered) {
110888                     return;
110889                 }
110890
110891                 // Get the height of the highest of both HeaderContainers
110892                 otherHeight = otherHeaderCt.layout.getRenderTarget().getViewSize().height;
110893                 if (!otherHeight) {
110894                     return;
110895                 }
110896                 thisHeight = this.getRenderTarget().getViewSize().height;
110897                 if (!thisHeight) {
110898                     return;
110899                 }
110900
110901                 // Prevent recursion back into here when the "other" grid, after adjusting to the new hight of its headerCt, attempts to inform its ownerCt
110902                 // Block the upward notification by flagging the top grid's component layout as busy.
110903                 topGrid.componentLayout.layoutBusy = true;
110904
110905                 // Assume that the correct header height is the height of this HeaderContainer
110906                 headerHeight = thisHeight;
110907
110908                 // Synch the height of the smaller HeaderContainer to the height of the highest one.
110909                 if (thisHeight > otherHeight) {
110910                     otherHeaderCt.layout.align = 'stretch';
110911                     otherHeaderCt.setCalculatedSize(otherHeaderCt.getWidth(), owner.getHeight(), otherHeaderCt.ownerCt);
110912                     delete otherHeaderCt.layout.align;
110913                     modifiedGrid = otherHeaderCt.up('tablepanel');
110914                 } else if (otherHeight > thisHeight) {
110915                     headerHeight = otherHeight;
110916                     this.align = 'stretch';
110917                     owner.setCalculatedSize(owner.getWidth(), otherHeaderCt.getHeight(), owner.ownerCt);
110918                     delete this.align;
110919                     modifiedGrid = owner.up('tablepanel');
110920                 }
110921                 topGrid.componentLayout.layoutBusy = false;
110922
110923                 // Gather all Header items across both Grids.
110924                 items = bothHeaderCts[0].layout.getLayoutItems().concat(bothHeaderCts[1].layout.getLayoutItems());
110925             } else {
110926                 headerHeight = this.getRenderTarget().getViewSize().height;
110927                 items = me.getLayoutItems();
110928             }
110929
110930             len = items.length;
110931             for (; i < len; i++) {
110932                 items[i].setPadding(headerHeight);
110933             }
110934
110935             // Size the View within the grid which has had its HeaderContainer entallened (That's a perfectly cromulent word BTW)
110936             if (modifiedGrid) {
110937                 setTimeout(function() {
110938                     modifiedGrid.doLayout();
110939                 }, 1);
110940             }
110941         }
110942     },
110943
110944     // FIX: when flexing we actually don't have enough space as we would
110945     // typically because of the scrollOffset on the GridView, must reserve this
110946     updateInnerCtSize: function(tSize, calcs) {
110947         var me = this,
110948             extra;
110949
110950         // Columns must not account for scroll offset
110951         if (!me.isHeader) {
110952             me.tooNarrow = calcs.meta.tooNarrow;
110953             extra = (me.reserveOffset ? me.availableSpaceOffset : 0);
110954
110955             if (calcs.meta.tooNarrow) {
110956                 tSize.width = calcs.meta.desiredSize + extra;
110957             } else {
110958                 tSize.width += extra;
110959             }
110960         }
110961
110962         return me.callParent(arguments);
110963     },
110964
110965     doOwnerCtLayouts: function() {
110966         var ownerCt = this.owner.ownerCt;
110967         if (!ownerCt.componentLayout.layoutBusy) {
110968             ownerCt.doComponentLayout();
110969         }
110970     }
110971 });
110972 /**
110973  * @class Ext.grid.LockingView
110974  * This class is used internally to provide a single interface when using
110975  * a locking grid. Internally, the locking grid creates two separate grids,
110976  * so this class is used to map calls appropriately.
110977  * @ignore
110978  */
110979 Ext.define('Ext.grid.LockingView', {
110980
110981     mixins: {
110982         observable: 'Ext.util.Observable'
110983     },
110984
110985     eventRelayRe: /^(beforeitem|beforecontainer|item|container|cell)/,
110986
110987     constructor: function(config){
110988         var me = this,
110989             eventNames = [],
110990             eventRe = me.eventRelayRe,
110991             locked = config.locked.getView(),
110992             normal = config.normal.getView(),
110993             events,
110994             event;
110995
110996         Ext.apply(me, {
110997             lockedView: locked,
110998             normalView: normal,
110999             lockedGrid: config.locked,
111000             normalGrid: config.normal,
111001             panel: config.panel
111002         });
111003         me.mixins.observable.constructor.call(me, config);
111004
111005         // relay events
111006         events = locked.events;
111007         for (event in events) {
111008             if (events.hasOwnProperty(event) && eventRe.test(event)) {
111009                 eventNames.push(event);
111010             }
111011         }
111012         me.relayEvents(locked, eventNames);
111013         me.relayEvents(normal, eventNames);
111014
111015         normal.on({
111016             scope: me,
111017             itemmouseleave: me.onItemMouseLeave,
111018             itemmouseenter: me.onItemMouseEnter
111019         });
111020
111021         locked.on({
111022             scope: me,
111023             itemmouseleave: me.onItemMouseLeave,
111024             itemmouseenter: me.onItemMouseEnter
111025         });
111026     },
111027
111028     getGridColumns: function() {
111029         var cols = this.lockedGrid.headerCt.getGridColumns();
111030         return cols.concat(this.normalGrid.headerCt.getGridColumns());
111031     },
111032
111033     getEl: function(column){
111034         return this.getViewForColumn(column).getEl();
111035     },
111036
111037     getViewForColumn: function(column) {
111038         var view = this.lockedView,
111039             inLocked;
111040
111041         view.headerCt.cascade(function(col){
111042             if (col === column) {
111043                 inLocked = true;
111044                 return false;
111045             }
111046         });
111047
111048         return inLocked ? view : this.normalView;
111049     },
111050
111051     onItemMouseEnter: function(view, record){
111052         var me = this,
111053             locked = me.lockedView,
111054             other = me.normalView,
111055             item;
111056
111057         if (view.trackOver) {
111058             if (view !== locked) {
111059                 other = locked;
111060             }
111061             item = other.getNode(record);
111062             other.highlightItem(item);
111063         }
111064     },
111065
111066     onItemMouseLeave: function(view, record){
111067         var me = this,
111068             locked = me.lockedView,
111069             other = me.normalView;
111070
111071         if (view.trackOver) {
111072             if (view !== locked) {
111073                 other = locked;
111074             }
111075             other.clearHighlight();
111076         }
111077     },
111078
111079     relayFn: function(name, args){
111080         args = args || [];
111081
111082         var view = this.lockedView;
111083         view[name].apply(view, args || []);
111084         view = this.normalView;
111085         view[name].apply(view, args || []);
111086     },
111087
111088     getSelectionModel: function(){
111089         return this.panel.getSelectionModel();
111090     },
111091
111092     getStore: function(){
111093         return this.panel.store;
111094     },
111095
111096     getNode: function(nodeInfo){
111097         // default to the normal view
111098         return this.normalView.getNode(nodeInfo);
111099     },
111100
111101     getCell: function(record, column){
111102         var view = this.getViewForColumn(column),
111103             row;
111104
111105         row = view.getNode(record);
111106         return Ext.fly(row).down(column.getCellSelector());
111107     },
111108
111109     getRecord: function(node){
111110         var result = this.lockedView.getRecord(node);
111111         if (!node) {
111112             result = this.normalView.getRecord(node);
111113         }
111114         return result;
111115     },
111116
111117     addElListener: function(eventName, fn, scope){
111118         this.relayFn('addElListener', arguments);
111119     },
111120
111121     refreshNode: function(){
111122         this.relayFn('refreshNode', arguments);
111123     },
111124
111125     refresh: function(){
111126         this.relayFn('refresh', arguments);
111127     },
111128
111129     bindStore: function(){
111130         this.relayFn('bindStore', arguments);
111131     },
111132
111133     addRowCls: function(){
111134         this.relayFn('addRowCls', arguments);
111135     },
111136
111137     removeRowCls: function(){
111138         this.relayFn('removeRowCls', arguments);
111139     }
111140
111141 });
111142 /**
111143  * @class Ext.grid.Lockable
111144  * @private
111145  *
111146  * Lockable is a private mixin which injects lockable behavior into any
111147  * TablePanel subclass such as GridPanel or TreePanel. TablePanel will
111148  * automatically inject the Ext.grid.Lockable mixin in when one of the
111149  * these conditions are met:
111150  *
111151  *  - The TablePanel has the lockable configuration set to true
111152  *  - One of the columns in the TablePanel has locked set to true/false
111153  *
111154  * Each TablePanel subclass must register an alias. It should have an array
111155  * of configurations to copy to the 2 separate tablepanel's that will be generated
111156  * to note what configurations should be copied. These are named normalCfgCopy and
111157  * lockedCfgCopy respectively.
111158  *
111159  * Columns which are locked must specify a fixed width. They do NOT support a
111160  * flex width.
111161  *
111162  * Configurations which are specified in this class will be available on any grid or
111163  * tree which is using the lockable functionality.
111164  */
111165 Ext.define('Ext.grid.Lockable', {
111166
111167     requires: ['Ext.grid.LockingView'],
111168
111169     /**
111170      * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
111171      * locked grid view. This is turned on by default. If your grid is guaranteed
111172      * to have rows of all the same height, you should set this to false to
111173      * optimize performance.
111174      */
111175     syncRowHeight: true,
111176
111177     /**
111178      * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
111179      * not specified lockable will determine the subgrid xtype to create by the
111180      * following rule. Use the superclasses xtype if the superclass is NOT
111181      * tablepanel, otherwise use the xtype itself.
111182      */
111183
111184     /**
111185      * @cfg {Object} lockedViewConfig A view configuration to be applied to the
111186      * locked side of the grid. Any conflicting configurations between lockedViewConfig
111187      * and viewConfig will be overwritten by the lockedViewConfig.
111188      */
111189
111190     /**
111191      * @cfg {Object} normalViewConfig A view configuration to be applied to the
111192      * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
111193      * and viewConfig will be overwritten by the normalViewConfig.
111194      */
111195
111196     // private variable to track whether or not the spacer is hidden/visible
111197     spacerHidden: true,
111198
111199     headerCounter: 0,
111200
111201     // i8n text
111202     unlockText: 'Unlock',
111203     lockText: 'Lock',
111204
111205     determineXTypeToCreate: function() {
111206         var me = this,
111207             typeToCreate;
111208
111209         if (me.subGridXType) {
111210             typeToCreate = me.subGridXType;
111211         } else {
111212             var xtypes     = this.getXTypes().split('/'),
111213                 xtypesLn   = xtypes.length,
111214                 xtype      = xtypes[xtypesLn - 1],
111215                 superxtype = xtypes[xtypesLn - 2];
111216
111217             if (superxtype !== 'tablepanel') {
111218                 typeToCreate = superxtype;
111219             } else {
111220                 typeToCreate = xtype;
111221             }
111222         }
111223
111224         return typeToCreate;
111225     },
111226
111227     // injectLockable will be invoked before initComponent's parent class implementation
111228     // is called, so throughout this method this. are configurations
111229     injectLockable: function() {
111230         // ensure lockable is set to true in the TablePanel
111231         this.lockable = true;
111232         // Instruct the TablePanel it already has a view and not to create one.
111233         // We are going to aggregate 2 copies of whatever TablePanel we are using
111234         this.hasView = true;
111235
111236         var me = this,
111237             // xtype of this class, 'treepanel' or 'gridpanel'
111238             // (Note: this makes it a requirement that any subclass that wants to use lockable functionality needs to register an
111239             // alias.)
111240             xtype = me.determineXTypeToCreate(),
111241             // share the selection model
111242             selModel = me.getSelectionModel(),
111243             lockedGrid = {
111244                 xtype: xtype,
111245                 // Lockable does NOT support animations for Tree
111246                 enableAnimations: false,
111247                 scroll: false,
111248                 scrollerOwner: false,
111249                 selModel: selModel,
111250                 border: false,
111251                 cls: Ext.baseCSSPrefix + 'grid-inner-locked'
111252             },
111253             normalGrid = {
111254                 xtype: xtype,
111255                 enableAnimations: false,
111256                 scrollerOwner: false,
111257                 selModel: selModel,
111258                 border: false
111259             },
111260             i = 0,
111261             columns,
111262             lockedHeaderCt,
111263             normalHeaderCt;
111264
111265         me.addCls(Ext.baseCSSPrefix + 'grid-locked');
111266
111267         // copy appropriate configurations to the respective
111268         // aggregated tablepanel instances and then delete them
111269         // from the master tablepanel.
111270         Ext.copyTo(normalGrid, me, me.normalCfgCopy);
111271         Ext.copyTo(lockedGrid, me, me.lockedCfgCopy);
111272         for (; i < me.normalCfgCopy.length; i++) {
111273             delete me[me.normalCfgCopy[i]];
111274         }
111275         for (i = 0; i < me.lockedCfgCopy.length; i++) {
111276             delete me[me.lockedCfgCopy[i]];
111277         }
111278
111279         me.addEvents(
111280             /**
111281              * @event lockcolumn
111282              * Fires when a column is locked.
111283              * @param {Ext.grid.Panel} this The gridpanel.
111284              * @param {Ext.grid.column.Column} column The column being locked.
111285              */
111286             'lockcolumn',
111287
111288             /**
111289              * @event unlockcolumn
111290              * Fires when a column is unlocked.
111291              * @param {Ext.grid.Panel} this The gridpanel.
111292              * @param {Ext.grid.column.Column} column The column being unlocked.
111293              */
111294             'unlockcolumn'
111295         );
111296
111297         me.addStateEvents(['lockcolumn', 'unlockcolumn']);
111298
111299         me.lockedHeights = [];
111300         me.normalHeights = [];
111301
111302         columns = me.processColumns(me.columns);
111303
111304         lockedGrid.width = columns.lockedWidth + Ext.num(selModel.headerWidth, 0);
111305         lockedGrid.columns = columns.locked;
111306         normalGrid.columns = columns.normal;
111307
111308         me.store = Ext.StoreManager.lookup(me.store);
111309         lockedGrid.store = me.store;
111310         normalGrid.store = me.store;
111311
111312         // normal grid should flex the rest of the width
111313         normalGrid.flex = 1;
111314         lockedGrid.viewConfig = me.lockedViewConfig || {};
111315         lockedGrid.viewConfig.loadingUseMsg = false;
111316         normalGrid.viewConfig = me.normalViewConfig || {};
111317
111318         Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
111319         Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
111320
111321         me.normalGrid = Ext.ComponentManager.create(normalGrid);
111322         me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
111323
111324         me.view = Ext.create('Ext.grid.LockingView', {
111325             locked: me.lockedGrid,
111326             normal: me.normalGrid,
111327             panel: me
111328         });
111329
111330         if (me.syncRowHeight) {
111331             me.lockedGrid.getView().on({
111332                 refresh: me.onLockedGridAfterRefresh,
111333                 itemupdate: me.onLockedGridAfterUpdate,
111334                 scope: me
111335             });
111336
111337             me.normalGrid.getView().on({
111338                 refresh: me.onNormalGridAfterRefresh,
111339                 itemupdate: me.onNormalGridAfterUpdate,
111340                 scope: me
111341             });
111342         }
111343
111344         lockedHeaderCt = me.lockedGrid.headerCt;
111345         normalHeaderCt = me.normalGrid.headerCt;
111346
111347         lockedHeaderCt.lockedCt = true;
111348         lockedHeaderCt.lockableInjected = true;
111349         normalHeaderCt.lockableInjected = true;
111350
111351         lockedHeaderCt.on({
111352             columnshow: me.onLockedHeaderShow,
111353             columnhide: me.onLockedHeaderHide,
111354             columnmove: me.onLockedHeaderMove,
111355             sortchange: me.onLockedHeaderSortChange,
111356             columnresize: me.onLockedHeaderResize,
111357             scope: me
111358         });
111359
111360         normalHeaderCt.on({
111361             columnmove: me.onNormalHeaderMove,
111362             sortchange: me.onNormalHeaderSortChange,
111363             scope: me
111364         });
111365
111366         me.normalGrid.on({
111367             scrollershow: me.onScrollerShow,
111368             scrollerhide: me.onScrollerHide,
111369             scope: me
111370         });
111371
111372         me.lockedGrid.on('afterlayout', me.onLockedGridAfterLayout, me, {single: true});
111373
111374         me.modifyHeaderCt();
111375         me.items = [me.lockedGrid, me.normalGrid];
111376
111377         me.relayHeaderCtEvents(lockedHeaderCt);
111378         me.relayHeaderCtEvents(normalHeaderCt);
111379
111380         me.layout = {
111381             type: 'hbox',
111382             align: 'stretch'
111383         };
111384     },
111385
111386     processColumns: function(columns){
111387         // split apart normal and lockedWidths
111388         var i = 0,
111389             len = columns.length,
111390             lockedWidth = 1,
111391             lockedHeaders = [],
111392             normalHeaders = [],
111393             column;
111394
111395         for (; i < len; ++i) {
111396             column = columns[i];
111397             // mark the column as processed so that the locked attribute does not
111398             // trigger trying to aggregate the columns again.
111399             column.processed = true;
111400             if (column.locked) {
111401                 if (!column.hidden) {
111402                     lockedWidth += column.width || Ext.grid.header.Container.prototype.defaultWidth;
111403                 }
111404                 lockedHeaders.push(column);
111405             } else {
111406                 normalHeaders.push(column);
111407             }
111408             if (!column.headerId) {
111409                 column.headerId = (column.initialConfig || column).id || ('L' + (++this.headerCounter));
111410             }
111411         }
111412         return {
111413             lockedWidth: lockedWidth,
111414             locked: lockedHeaders,
111415             normal: normalHeaders
111416         };
111417     },
111418
111419     // create a new spacer after the table is refreshed
111420     onLockedGridAfterLayout: function() {
111421         var me         = this,
111422             lockedView = me.lockedGrid.getView();
111423         lockedView.on({
111424             beforerefresh: me.destroySpacer,
111425             scope: me
111426         });
111427     },
111428
111429     // trigger a pseudo refresh on the normal side
111430     onLockedHeaderMove: function() {
111431         if (this.syncRowHeight) {
111432             this.onNormalGridAfterRefresh();
111433         }
111434     },
111435
111436     // trigger a pseudo refresh on the locked side
111437     onNormalHeaderMove: function() {
111438         if (this.syncRowHeight) {
111439             this.onLockedGridAfterRefresh();
111440         }
111441     },
111442
111443     // create a spacer in lockedsection and store a reference
111444     // TODO: Should destroy before refreshing content
111445     getSpacerEl: function() {
111446         var me   = this,
111447             w,
111448             view,
111449             el;
111450
111451         if (!me.spacerEl) {
111452             // This affects scrolling all the way to the bottom of a locked grid
111453             // additional test, sort a column and make sure it synchronizes
111454             w    = Ext.getScrollBarWidth() + (Ext.isIE ? 2 : 0);
111455             view = me.lockedGrid.getView();
111456             el   = view.el;
111457
111458             me.spacerEl = Ext.DomHelper.append(el, {
111459                 cls: me.spacerHidden ? (Ext.baseCSSPrefix + 'hidden') : '',
111460                 style: 'height: ' + w + 'px;'
111461             }, true);
111462         }
111463         return me.spacerEl;
111464     },
111465
111466     destroySpacer: function() {
111467         var me = this;
111468         if (me.spacerEl) {
111469             me.spacerEl.destroy();
111470             delete me.spacerEl;
111471         }
111472     },
111473
111474     // cache the heights of all locked rows and sync rowheights
111475     onLockedGridAfterRefresh: function() {
111476         var me     = this,
111477             view   = me.lockedGrid.getView(),
111478             el     = view.el,
111479             rowEls = el.query(view.getItemSelector()),
111480             ln     = rowEls.length,
111481             i = 0;
111482
111483         // reset heights each time.
111484         me.lockedHeights = [];
111485
111486         for (; i < ln; i++) {
111487             me.lockedHeights[i] = rowEls[i].clientHeight;
111488         }
111489         me.syncRowHeights();
111490     },
111491
111492     // cache the heights of all normal rows and sync rowheights
111493     onNormalGridAfterRefresh: function() {
111494         var me     = this,
111495             view   = me.normalGrid.getView(),
111496             el     = view.el,
111497             rowEls = el.query(view.getItemSelector()),
111498             ln     = rowEls.length,
111499             i = 0;
111500
111501         // reset heights each time.
111502         me.normalHeights = [];
111503
111504         for (; i < ln; i++) {
111505             me.normalHeights[i] = rowEls[i].clientHeight;
111506         }
111507         me.syncRowHeights();
111508     },
111509
111510     // rows can get bigger/smaller
111511     onLockedGridAfterUpdate: function(record, index, node) {
111512         this.lockedHeights[index] = node.clientHeight;
111513         this.syncRowHeights();
111514     },
111515
111516     // rows can get bigger/smaller
111517     onNormalGridAfterUpdate: function(record, index, node) {
111518         this.normalHeights[index] = node.clientHeight;
111519         this.syncRowHeights();
111520     },
111521
111522     // match the rowheights to the biggest rowheight on either
111523     // side
111524     syncRowHeights: function() {
111525         var me = this,
111526             lockedHeights = me.lockedHeights,
111527             normalHeights = me.normalHeights,
111528             calcHeights   = [],
111529             ln = lockedHeights.length,
111530             i  = 0,
111531             lockedView, normalView,
111532             lockedRowEls, normalRowEls,
111533             vertScroller = me.getVerticalScroller(),
111534             scrollTop;
111535
111536         // ensure there are an equal num of locked and normal
111537         // rows before synchronization
111538         if (lockedHeights.length && normalHeights.length) {
111539             lockedView = me.lockedGrid.getView();
111540             normalView = me.normalGrid.getView();
111541             lockedRowEls = lockedView.el.query(lockedView.getItemSelector());
111542             normalRowEls = normalView.el.query(normalView.getItemSelector());
111543
111544             // loop thru all of the heights and sync to the other side
111545             for (; i < ln; i++) {
111546                 // ensure both are numbers
111547                 if (!isNaN(lockedHeights[i]) && !isNaN(normalHeights[i])) {
111548                     if (lockedHeights[i] > normalHeights[i]) {
111549                         Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
111550                     } else if (lockedHeights[i] < normalHeights[i]) {
111551                         Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
111552                     }
111553                 }
111554             }
111555
111556             // invalidate the scroller and sync the scrollers
111557             me.normalGrid.invalidateScroller();
111558
111559             // synchronize the view with the scroller, if we have a virtualScrollTop
111560             // then the user is using a PagingScroller
111561             if (vertScroller && vertScroller.setViewScrollTop) {
111562                 vertScroller.setViewScrollTop(me.virtualScrollTop);
111563             } else {
111564                 // We don't use setScrollTop here because if the scrollTop is
111565                 // set to the exact same value some browsers won't fire the scroll
111566                 // event. Instead, we directly set the scrollTop.
111567                 scrollTop = normalView.el.dom.scrollTop;
111568                 normalView.el.dom.scrollTop = scrollTop;
111569                 lockedView.el.dom.scrollTop = scrollTop;
111570             }
111571
111572             // reset the heights
111573             me.lockedHeights = [];
111574             me.normalHeights = [];
111575         }
111576     },
111577
111578     // track when scroller is shown
111579     onScrollerShow: function(scroller, direction) {
111580         if (direction === 'horizontal') {
111581             this.spacerHidden = false;
111582             this.getSpacerEl().removeCls(Ext.baseCSSPrefix + 'hidden');
111583         }
111584     },
111585
111586     // track when scroller is hidden
111587     onScrollerHide: function(scroller, direction) {
111588         if (direction === 'horizontal') {
111589             this.spacerHidden = true;
111590             if (this.spacerEl) {
111591                 this.spacerEl.addCls(Ext.baseCSSPrefix + 'hidden');
111592             }
111593         }
111594     },
111595
111596
111597     // inject Lock and Unlock text
111598     modifyHeaderCt: function() {
111599         var me = this;
111600         me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(true);
111601         me.normalGrid.headerCt.getMenuItems = me.getMenuItems(false);
111602     },
111603
111604     onUnlockMenuClick: function() {
111605         this.unlock();
111606     },
111607
111608     onLockMenuClick: function() {
111609         this.lock();
111610     },
111611
111612     getMenuItems: function(locked) {
111613         var me            = this,
111614             unlockText    = me.unlockText,
111615             lockText      = me.lockText,
111616             unlockCls     = Ext.baseCSSPrefix + 'hmenu-unlock',
111617             lockCls       = Ext.baseCSSPrefix + 'hmenu-lock',
111618             unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
111619             lockHandler   = Ext.Function.bind(me.onLockMenuClick, me);
111620
111621         // runs in the scope of headerCt
111622         return function() {
111623             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
111624             o.push('-',{
111625                 cls: unlockCls,
111626                 text: unlockText,
111627                 handler: unlockHandler,
111628                 disabled: !locked
111629             });
111630             o.push({
111631                 cls: lockCls,
111632                 text: lockText,
111633                 handler: lockHandler,
111634                 disabled: locked
111635             });
111636             return o;
111637         };
111638     },
111639
111640     // going from unlocked section to locked
111641     /**
111642      * Locks the activeHeader as determined by which menu is open OR a header
111643      * as specified.
111644      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
111645      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to appending as the last item.
111646      * @private
111647      */
111648     lock: function(activeHd, toIdx) {
111649         var me         = this,
111650             normalGrid = me.normalGrid,
111651             lockedGrid = me.lockedGrid,
111652             normalHCt  = normalGrid.headerCt,
111653             lockedHCt  = lockedGrid.headerCt;
111654
111655         activeHd = activeHd || normalHCt.getMenu().activeHeader;
111656
111657         // if column was previously flexed, get/set current width
111658         // and remove the flex
111659         if (activeHd.flex) {
111660             activeHd.width = activeHd.getWidth();
111661             delete activeHd.flex;
111662         }
111663
111664         normalHCt.remove(activeHd, false);
111665         lockedHCt.suspendLayout = true;
111666         activeHd.locked = true;
111667         if (Ext.isDefined(toIdx)) {
111668             lockedHCt.insert(toIdx, activeHd);
111669         } else {
111670             lockedHCt.add(activeHd);
111671         }
111672         lockedHCt.suspendLayout = false;
111673         me.syncLockedSection();
111674
111675         me.fireEvent('lockcolumn', me, activeHd);
111676     },
111677
111678     syncLockedSection: function() {
111679         var me = this;
111680         me.syncLockedWidth();
111681         me.lockedGrid.getView().refresh();
111682         me.normalGrid.getView().refresh();
111683     },
111684
111685     // adjust the locked section to the width of its respective
111686     // headerCt
111687     syncLockedWidth: function() {
111688         var me = this,
111689             width = me.lockedGrid.headerCt.getFullWidth(true);
111690         me.lockedGrid.setWidth(width+1); // +1 for border pixel
111691         me.doComponentLayout();
111692     },
111693
111694     onLockedHeaderResize: function() {
111695         this.syncLockedWidth();
111696     },
111697
111698     onLockedHeaderHide: function() {
111699         this.syncLockedWidth();
111700     },
111701
111702     onLockedHeaderShow: function() {
111703         this.syncLockedWidth();
111704     },
111705
111706     onLockedHeaderSortChange: function(headerCt, header, sortState) {
111707         if (sortState) {
111708             // no real header, and silence the event so we dont get into an
111709             // infinite loop
111710             this.normalGrid.headerCt.clearOtherSortStates(null, true);
111711         }
111712     },
111713
111714     onNormalHeaderSortChange: function(headerCt, header, sortState) {
111715         if (sortState) {
111716             // no real header, and silence the event so we dont get into an
111717             // infinite loop
111718             this.lockedGrid.headerCt.clearOtherSortStates(null, true);
111719         }
111720     },
111721
111722     // going from locked section to unlocked
111723     /**
111724      * Unlocks the activeHeader as determined by which menu is open OR a header
111725      * as specified.
111726      * @param {Ext.grid.column.Column} header (Optional) Header to unlock from the locked section. Defaults to the header which has the menu open currently.
111727      * @param {Number} toIdx (Optional) The index to move the unlocked header to. Defaults to 0.
111728      * @private
111729      */
111730     unlock: function(activeHd, toIdx) {
111731         var me         = this,
111732             normalGrid = me.normalGrid,
111733             lockedGrid = me.lockedGrid,
111734             normalHCt  = normalGrid.headerCt,
111735             lockedHCt  = lockedGrid.headerCt;
111736
111737         if (!Ext.isDefined(toIdx)) {
111738             toIdx = 0;
111739         }
111740         activeHd = activeHd || lockedHCt.getMenu().activeHeader;
111741
111742         lockedHCt.remove(activeHd, false);
111743         me.syncLockedWidth();
111744         me.lockedGrid.getView().refresh();
111745         activeHd.locked = false;
111746         normalHCt.insert(toIdx, activeHd);
111747         me.normalGrid.getView().refresh();
111748
111749         me.fireEvent('unlockcolumn', me, activeHd);
111750     },
111751
111752     applyColumnsState: function (columns) {
111753         var me = this,
111754             lockedGrid = me.lockedGrid,
111755             lockedHeaderCt = lockedGrid.headerCt,
111756             normalHeaderCt = me.normalGrid.headerCt,
111757             lockedCols = lockedHeaderCt.items,
111758             normalCols = normalHeaderCt.items,
111759             existing,
111760             locked = [],
111761             normal = [],
111762             lockedDefault,
111763             lockedWidth = 1;
111764
111765         Ext.each(columns, function (col) {
111766             function matches (item) {
111767                 return item.headerId == col.id;
111768             }
111769
111770             lockedDefault = true;
111771             if (!(existing = lockedCols.findBy(matches))) {
111772                 existing = normalCols.findBy(matches);
111773                 lockedDefault = false;
111774             }
111775
111776             if (existing) {
111777                 if (existing.applyColumnState) {
111778                     existing.applyColumnState(col);
111779                 }
111780                 if (!Ext.isDefined(existing.locked)) {
111781                     existing.locked = lockedDefault;
111782                 }
111783                 if (existing.locked) {
111784                     locked.push(existing);
111785                     if (!existing.hidden && Ext.isNumber(existing.width)) {
111786                         lockedWidth += existing.width;
111787                     }
111788                 } else {
111789                     normal.push(existing);
111790                 }
111791             }
111792         });
111793
111794         // state and config must have the same columns (compare counts for now):
111795         if (locked.length + normal.length == lockedCols.getCount() + normalCols.getCount()) {
111796             lockedHeaderCt.removeAll(false);
111797             normalHeaderCt.removeAll(false);
111798
111799             lockedHeaderCt.add(locked);
111800             normalHeaderCt.add(normal);
111801
111802             lockedGrid.setWidth(lockedWidth);
111803         }
111804     },
111805
111806     getColumnsState: function () {
111807         var me = this,
111808             locked = me.lockedGrid.headerCt.getColumnsState(),
111809             normal = me.normalGrid.headerCt.getColumnsState();
111810
111811         return locked.concat(normal);
111812     },
111813
111814     // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
111815     reconfigureLockable: function(store, columns) {
111816         var me = this,
111817             lockedGrid = me.lockedGrid,
111818             normalGrid = me.normalGrid;
111819
111820         if (columns) {
111821             lockedGrid.headerCt.suspendLayout = true;
111822             normalGrid.headerCt.suspendLayout = true;
111823             lockedGrid.headerCt.removeAll();
111824             normalGrid.headerCt.removeAll();
111825
111826             columns = me.processColumns(columns);
111827             lockedGrid.setWidth(columns.lockedWidth);
111828             lockedGrid.headerCt.add(columns.locked);
111829             normalGrid.headerCt.add(columns.normal);
111830         }
111831
111832         if (store) {
111833             store = Ext.data.StoreManager.lookup(store);
111834             me.store = store;
111835             lockedGrid.bindStore(store);
111836             normalGrid.bindStore(store);
111837         } else {
111838             lockedGrid.getView().refresh();
111839             normalGrid.getView().refresh();
111840         }
111841
111842         if (columns) {
111843             lockedGrid.headerCt.suspendLayout = false;
111844             normalGrid.headerCt.suspendLayout = false;
111845             lockedGrid.headerCt.forceComponentLayout();
111846             normalGrid.headerCt.forceComponentLayout();
111847         }
111848     }
111849 });
111850
111851 /**
111852  * Docked in an Ext.grid.Panel, controls virtualized scrolling and synchronization
111853  * across different sections.
111854  */
111855 Ext.define('Ext.grid.Scroller', {
111856     extend: 'Ext.Component',
111857     alias: 'widget.gridscroller',
111858     weight: 110,
111859     baseCls: Ext.baseCSSPrefix + 'scroller',
111860     focusable: false,
111861     reservedSpace: 0,
111862
111863     renderTpl: [
111864         '<div class="' + Ext.baseCSSPrefix + 'scroller-ct" id="{baseId}_ct">',
111865             '<div class="' + Ext.baseCSSPrefix + 'stretcher" id="{baseId}_stretch"></div>',
111866         '</div>'
111867     ],
111868
111869     initComponent: function() {
111870         var me       = this,
111871             dock     = me.dock,
111872             cls      = Ext.baseCSSPrefix + 'scroller-vertical';
111873
111874         me.offsets = {bottom: 0};
111875         me.scrollProp = 'scrollTop';
111876         me.vertical = true;
111877         me.sizeProp = 'width';
111878
111879         if (dock === 'top' || dock === 'bottom') {
111880             cls = Ext.baseCSSPrefix + 'scroller-horizontal';
111881             me.sizeProp = 'height';
111882             me.scrollProp = 'scrollLeft';
111883             me.vertical = false;
111884             me.weight += 5;
111885         }
111886
111887         me.cls += (' ' + cls);
111888
111889         Ext.applyIf(me.renderSelectors, {
111890             stretchEl: '.' + Ext.baseCSSPrefix + 'stretcher',
111891             scrollEl: '.' + Ext.baseCSSPrefix + 'scroller-ct'
111892         });
111893         me.callParent();
111894     },
111895     
111896     ensureDimension: function(){
111897         var me = this,
111898             sizeProp = me.sizeProp;
111899             
111900         me[sizeProp] = me.scrollerSize = Ext.getScrollbarSize()[sizeProp];  
111901     },
111902
111903     initRenderData: function () {
111904         var me = this,
111905             ret = me.callParent(arguments) || {};
111906
111907         ret.baseId = me.id;
111908
111909         return ret;
111910     },
111911
111912     afterRender: function() {
111913         var me = this;
111914         me.callParent();
111915         
111916         me.mon(me.scrollEl, 'scroll', me.onElScroll, me);
111917         Ext.cache[me.el.id].skipGarbageCollection = true;
111918     },
111919
111920     onAdded: function(container) {
111921         // Capture the controlling grid Panel so that we can use it even when we are undocked, and don't have an ownerCt
111922         this.ownerGrid = container;
111923         this.callParent(arguments);
111924     },
111925
111926     getSizeCalculation: function() {
111927         var me     = this,
111928             owner  = me.getPanel(),
111929             width  = 1,
111930             height = 1,
111931             view, tbl;
111932
111933         if (!me.vertical) {
111934             // TODO: Must gravitate to a single region..
111935             // Horizontal scrolling only scrolls virtualized region
111936             var items  = owner.query('tableview'),
111937                 center = items[1] || items[0];
111938
111939             if (!center) {
111940                 return false;
111941             }
111942             // center is not guaranteed to have content, such as when there
111943             // are zero rows in the grid/tree. We read the width from the
111944             // headerCt instead.
111945             width = center.headerCt.getFullWidth();
111946
111947             if (Ext.isIEQuirks) {
111948                 width--;
111949             }
111950         } else {
111951             view = owner.down('tableview:not([lockableInjected])');
111952             if (!view || !view.el) {
111953                 return false;
111954             }
111955             tbl = view.el.child('table', true);
111956             if (!tbl) {
111957                 return false;
111958             }
111959
111960             // needs to also account for header and scroller (if still in picture)
111961             // should calculate from headerCt.
111962             height = tbl.offsetHeight;
111963         }
111964         if (isNaN(width)) {
111965             width = 1;
111966         }
111967         if (isNaN(height)) {
111968             height = 1;
111969         }
111970         return {
111971             width: width,
111972             height: height
111973         };
111974     },
111975
111976     invalidate: function(firstPass) {
111977         var me = this,
111978             stretchEl = me.stretchEl;
111979
111980         if (!stretchEl || !me.ownerCt) {
111981             return;
111982         }
111983
111984         var size  = me.getSizeCalculation(),
111985             scrollEl = me.scrollEl,
111986             elDom = scrollEl.dom,
111987             reservedSpace = me.reservedSpace,
111988             pos,
111989             extra = 5;
111990
111991         if (size) {
111992             stretchEl.setSize(size);
111993
111994             size = me.el.getSize(true);
111995
111996             if (me.vertical) {
111997                 size.width += extra;
111998                 size.height -= reservedSpace;
111999                 pos = 'left';
112000             } else {
112001                 size.width -= reservedSpace;
112002                 size.height += extra;
112003                 pos = 'top';
112004             }
112005
112006             scrollEl.setSize(size);
112007             elDom.style[pos] = (-extra) + 'px';
112008
112009             // BrowserBug: IE7
112010             // This makes the scroller enabled, when initially rendering.
112011             elDom.scrollTop = elDom.scrollTop;
112012         }
112013     },
112014
112015     afterComponentLayout: function() {
112016         this.callParent(arguments);
112017         this.invalidate();
112018     },
112019
112020     restoreScrollPos: function () {
112021         var me = this,
112022             el = this.scrollEl,
112023             elDom = el && el.dom;
112024
112025         if (me._scrollPos !== null && elDom) {
112026             elDom[me.scrollProp] = me._scrollPos;
112027             me._scrollPos = null;
112028         }
112029     },
112030
112031     setReservedSpace: function (reservedSpace) {
112032         var me = this;
112033         if (me.reservedSpace !== reservedSpace) {
112034             me.reservedSpace = reservedSpace;
112035             me.invalidate();
112036         }
112037     },
112038
112039     saveScrollPos: function () {
112040         var me = this,
112041             el = this.scrollEl,
112042             elDom = el && el.dom;
112043
112044         me._scrollPos = elDom ? elDom[me.scrollProp] : null;
112045     },
112046
112047     /**
112048      * Sets the scrollTop and constrains the value between 0 and max.
112049      * @param {Number} scrollTop
112050      * @return {Number} The resulting scrollTop value after being constrained
112051      */
112052     setScrollTop: function(scrollTop) {
112053         var el = this.scrollEl,
112054             elDom = el && el.dom;
112055
112056         if (elDom) {
112057             return elDom.scrollTop = Ext.Number.constrain(scrollTop, 0, elDom.scrollHeight - elDom.clientHeight);
112058         }
112059     },
112060
112061     /**
112062      * Sets the scrollLeft and constrains the value between 0 and max.
112063      * @param {Number} scrollLeft
112064      * @return {Number} The resulting scrollLeft value after being constrained
112065      */
112066     setScrollLeft: function(scrollLeft) {
112067         var el = this.scrollEl,
112068             elDom = el && el.dom;
112069
112070         if (elDom) {
112071             return elDom.scrollLeft = Ext.Number.constrain(scrollLeft, 0, elDom.scrollWidth - elDom.clientWidth);
112072         }
112073     },
112074
112075     /**
112076      * Scroll by deltaY
112077      * @param {Number} delta
112078      * @return {Number} The resulting scrollTop value
112079      */
112080     scrollByDeltaY: function(delta) {
112081         var el = this.scrollEl,
112082             elDom = el && el.dom;
112083
112084         if (elDom) {
112085             return this.setScrollTop(elDom.scrollTop + delta);
112086         }
112087     },
112088
112089     /**
112090      * Scroll by deltaX
112091      * @param {Number} delta
112092      * @return {Number} The resulting scrollLeft value
112093      */
112094     scrollByDeltaX: function(delta) {
112095         var el = this.scrollEl,
112096             elDom = el && el.dom;
112097
112098         if (elDom) {
112099             return this.setScrollLeft(elDom.scrollLeft + delta);
112100         }
112101     },
112102
112103
112104     /**
112105      * Scroll to the top.
112106      */
112107     scrollToTop : function(){
112108         this.setScrollTop(0);
112109     },
112110
112111     // synchronize the scroller with the bound gridviews
112112     onElScroll: function(event, target) {
112113         this.fireEvent('bodyscroll', event, target);
112114     },
112115
112116     getPanel: function() {
112117         var me = this;
112118         if (!me.panel) {
112119             me.panel = this.up('[scrollerOwner]');
112120         }
112121         return me.panel;
112122     }
112123 });
112124
112125
112126 /**
112127  * @class Ext.grid.PagingScroller
112128  * @extends Ext.grid.Scroller
112129  */
112130 Ext.define('Ext.grid.PagingScroller', {
112131     extend: 'Ext.grid.Scroller',
112132     alias: 'widget.paginggridscroller',
112133     //renderTpl: null,
112134     //tpl: [
112135     //    '<tpl for="pages">',
112136     //        '<div class="' + Ext.baseCSSPrefix + 'stretcher" style="width: {width}px;height: {height}px;"></div>',
112137     //    '</tpl>'
112138     //],
112139     /**
112140      * @cfg {Number} percentageFromEdge This is a number above 0 and less than 1 which specifies
112141      * at what percentage to begin fetching the next page. For example if the pageSize is 100
112142      * and the percentageFromEdge is the default of 0.35, the paging scroller will prefetch pages
112143      * when scrolling up between records 0 and 34 and when scrolling down between records 65 and 99.
112144      */
112145     percentageFromEdge: 0.35,
112146
112147     /**
112148      * @cfg {Number} scrollToLoadBuffer This is the time in milliseconds to buffer load requests
112149      * when scrolling the PagingScrollbar.
112150      */
112151     scrollToLoadBuffer: 200,
112152
112153     activePrefetch: true,
112154
112155     chunkSize: 50,
112156     snapIncrement: 25,
112157
112158     syncScroll: true,
112159
112160     initComponent: function() {
112161         var me = this,
112162             ds = me.store;
112163
112164         ds.on('guaranteedrange', me.onGuaranteedRange, me);
112165         me.callParent(arguments);
112166     },
112167
112168     onGuaranteedRange: function(range, start, end) {
112169         var me = this,
112170             ds = me.store,
112171             rs;
112172         // this should never happen
112173         if (range.length && me.visibleStart < range[0].index) {
112174             return;
112175         }
112176
112177         ds.loadRecords(range);
112178
112179         if (!me.firstLoad) {
112180             if (me.rendered) {
112181                 me.invalidate();
112182             } else {
112183                 me.on('afterrender', me.invalidate, me, {single: true});
112184             }
112185             me.firstLoad = true;
112186         } else {
112187             // adjust to visible
112188             // only sync if there is a paging scrollbar element and it has a scroll height (meaning it's currently in the DOM)
112189             if (me.scrollEl && me.scrollEl.dom && me.scrollEl.dom.scrollHeight) {
112190                 me.syncTo();
112191             }
112192         }
112193     },
112194
112195     syncTo: function() {
112196         var me            = this,
112197             pnl           = me.getPanel(),
112198             store         = pnl.store,
112199             scrollerElDom = this.scrollEl.dom,
112200             rowOffset     = me.visibleStart - store.guaranteedStart,
112201             scrollBy      = rowOffset * me.rowHeight,
112202             scrollHeight  = scrollerElDom.scrollHeight,
112203             clientHeight  = scrollerElDom.clientHeight,
112204             scrollTop     = scrollerElDom.scrollTop,
112205             useMaximum;
112206             
112207
112208         // BrowserBug: clientHeight reports 0 in IE9 StrictMode
112209         // Instead we are using offsetHeight and hardcoding borders
112210         if (Ext.isIE9 && Ext.isStrict) {
112211             clientHeight = scrollerElDom.offsetHeight + 2;
112212         }
112213
112214         // This should always be zero or greater than zero but staying
112215         // safe and less than 0 we'll scroll to the bottom.
112216         useMaximum = (scrollHeight - clientHeight - scrollTop <= 0);
112217         this.setViewScrollTop(scrollBy, useMaximum);
112218     },
112219
112220     getPageData : function(){
112221         var panel = this.getPanel(),
112222             store = panel.store,
112223             totalCount = store.getTotalCount();
112224
112225         return {
112226             total : totalCount,
112227             currentPage : store.currentPage,
112228             pageCount: Math.ceil(totalCount / store.pageSize),
112229             fromRecord: ((store.currentPage - 1) * store.pageSize) + 1,
112230             toRecord: Math.min(store.currentPage * store.pageSize, totalCount)
112231         };
112232     },
112233
112234     onElScroll: function(e, t) {
112235         var me = this,
112236             panel = me.getPanel(),
112237             store = panel.store,
112238             pageSize = store.pageSize,
112239             guaranteedStart = store.guaranteedStart,
112240             guaranteedEnd = store.guaranteedEnd,
112241             totalCount = store.getTotalCount(),
112242             numFromEdge = Math.ceil(me.percentageFromEdge * pageSize),
112243             position = t.scrollTop,
112244             visibleStart = Math.floor(position / me.rowHeight),
112245             view = panel.down('tableview'),
112246             viewEl = view.el,
112247             visibleHeight = viewEl.getHeight(),
112248             visibleAhead = Math.ceil(visibleHeight / me.rowHeight),
112249             visibleEnd = visibleStart + visibleAhead,
112250             prevPage = Math.floor(visibleStart / pageSize),
112251             nextPage = Math.floor(visibleEnd / pageSize) + 2,
112252             lastPage = Math.ceil(totalCount / pageSize),
112253             snap = me.snapIncrement,
112254             requestStart = Math.floor(visibleStart / snap) * snap,
112255             requestEnd = requestStart + pageSize - 1,
112256             activePrefetch = me.activePrefetch;
112257
112258         me.visibleStart = visibleStart;
112259         me.visibleEnd = visibleEnd;
112260         
112261         
112262         me.syncScroll = true;
112263         if (totalCount >= pageSize) {
112264             // end of request was past what the total is, grab from the end back a pageSize
112265             if (requestEnd > totalCount - 1) {
112266                 me.cancelLoad();
112267                 if (store.rangeSatisfied(totalCount - pageSize, totalCount - 1)) {
112268                     me.syncScroll = true;
112269                 }
112270                 store.guaranteeRange(totalCount - pageSize, totalCount - 1);
112271             // Out of range, need to reset the current data set
112272             } else if (visibleStart <= guaranteedStart || visibleEnd > guaranteedEnd) {
112273                 if (visibleStart <= guaranteedStart) {
112274                     // need to scroll up
112275                     requestStart -= snap;
112276                     requestEnd -= snap;
112277                     
112278                     if (requestStart < 0) {
112279                         requestStart = 0;
112280                         requestEnd = pageSize;
112281                     }
112282                 }
112283                 if (store.rangeSatisfied(requestStart, requestEnd)) {
112284                     me.cancelLoad();
112285                     store.guaranteeRange(requestStart, requestEnd);
112286                 } else {
112287                     store.mask();
112288                     me.attemptLoad(requestStart, requestEnd);
112289                 }
112290                 // dont sync the scroll view immediately, sync after the range has been guaranteed
112291                 me.syncScroll = false;
112292             } else if (activePrefetch && visibleStart < (guaranteedStart + numFromEdge) && prevPage > 0) {
112293                 me.syncScroll = true;
112294                 store.prefetchPage(prevPage);
112295             } else if (activePrefetch && visibleEnd > (guaranteedEnd - numFromEdge) && nextPage < lastPage) {
112296                 me.syncScroll = true;
112297                 store.prefetchPage(nextPage);
112298             }
112299         }
112300
112301         if (me.syncScroll) {
112302             me.syncTo();
112303         }
112304     },
112305
112306     getSizeCalculation: function() {
112307         // Use the direct ownerCt here rather than the scrollerOwner
112308         // because we are calculating widths/heights.
112309         var me     = this,
112310             owner  = me.ownerGrid,
112311             view   = owner.getView(),
112312             store  = me.store,
112313             dock   = me.dock,
112314             elDom  = me.el.dom,
112315             width  = 1,
112316             height = 1;
112317
112318         if (!me.rowHeight) {
112319             me.rowHeight = view.el.down(view.getItemSelector()).getHeight(false, true);
112320         }
112321
112322         // If the Store is *locally* filtered, use the filtered count from getCount.
112323         height = store[(!store.remoteFilter && store.isFiltered()) ? 'getCount' : 'getTotalCount']() * me.rowHeight;
112324
112325         if (isNaN(width)) {
112326             width = 1;
112327         }
112328         if (isNaN(height)) {
112329             height = 1;
112330         }
112331         return {
112332             width: width,
112333             height: height
112334         };
112335     },
112336
112337     attemptLoad: function(start, end) {
112338         var me = this;
112339         if (!me.loadTask) {
112340             me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
112341         }
112342         me.loadTask.delay(me.scrollToLoadBuffer, me.doAttemptLoad, me, [start, end]);
112343     },
112344
112345     cancelLoad: function() {
112346         if (this.loadTask) {
112347             this.loadTask.cancel();
112348         }
112349     },
112350
112351     doAttemptLoad:  function(start, end) {
112352         var store = this.getPanel().store;
112353         store.guaranteeRange(start, end);
112354     },
112355
112356     setViewScrollTop: function(scrollTop, useMax) {
112357         var me = this,
112358             owner = me.getPanel(),
112359             items = owner.query('tableview'),
112360             i = 0,
112361             len = items.length,
112362             center,
112363             centerEl,
112364             calcScrollTop,
112365             maxScrollTop,
112366             scrollerElDom = me.el.dom;
112367
112368         owner.virtualScrollTop = scrollTop;
112369
112370         center = items[1] || items[0];
112371         centerEl = center.el.dom;
112372
112373         maxScrollTop = ((owner.store.pageSize * me.rowHeight) - centerEl.clientHeight);
112374         calcScrollTop = (scrollTop % ((owner.store.pageSize * me.rowHeight) + 1));
112375         if (useMax) {
112376             calcScrollTop = maxScrollTop;
112377         }
112378         if (calcScrollTop > maxScrollTop) {
112379             //Ext.Error.raise("Calculated scrollTop was larger than maxScrollTop");
112380             return;
112381             // calcScrollTop = maxScrollTop;
112382         }
112383         for (; i < len; i++) {
112384             items[i].el.dom.scrollTop = calcScrollTop;
112385         }
112386     }
112387 });
112388
112389 /**
112390  * @author Nicolas Ferrero
112391  *
112392  * TablePanel is the basis of both {@link Ext.tree.Panel TreePanel} and {@link Ext.grid.Panel GridPanel}.
112393  *
112394  * TablePanel aggregates:
112395  *
112396  *  - a Selection Model
112397  *  - a View
112398  *  - a Store
112399  *  - Scrollers
112400  *  - Ext.grid.header.Container
112401  */
112402 Ext.define('Ext.panel.Table', {
112403     extend: 'Ext.panel.Panel',
112404
112405     alias: 'widget.tablepanel',
112406
112407     uses: [
112408         'Ext.selection.RowModel',
112409         'Ext.grid.Scroller',
112410         'Ext.grid.header.Container',
112411         'Ext.grid.Lockable'
112412     ],
112413
112414     extraBaseCls: Ext.baseCSSPrefix + 'grid',
112415     extraBodyCls: Ext.baseCSSPrefix + 'grid-body',
112416
112417     layout: 'fit',
112418     /**
112419      * @property {Boolean} hasView
112420      * True to indicate that a view has been injected into the panel.
112421      */
112422     hasView: false,
112423
112424     // each panel should dictate what viewType and selType to use
112425     /**
112426      * @cfg {String} viewType
112427      * An xtype of view to use. This is automatically set to 'gridview' by {@link Ext.grid.Panel Grid}
112428      * and to 'treeview' by {@link Ext.tree.Panel Tree}.
112429      */
112430     viewType: null,
112431
112432     /**
112433      * @cfg {Object} viewConfig
112434      * A config object that will be applied to the grid's UI view. Any of the config options available for
112435      * {@link Ext.view.Table} can be specified here. This option is ignored if {@link #view} is specified.
112436      */
112437
112438     /**
112439      * @cfg {Ext.view.Table} view
112440      * The {@link Ext.view.Table} used by the grid. Use {@link #viewConfig} to just supply some config options to
112441      * view (instead of creating an entire View instance).
112442      */
112443
112444     /**
112445      * @cfg {String} selType
112446      * An xtype of selection model to use. Defaults to 'rowmodel'. This is used to create selection model if just
112447      * a config object or nothing at all given in {@link #selModel} config.
112448      */
112449     selType: 'rowmodel',
112450
112451     /**
112452      * @cfg {Ext.selection.Model/Object} selModel
112453      * A {@link Ext.selection.Model selection model} instance or config object.  In latter case the {@link #selType}
112454      * config option determines to which type of selection model this config is applied.
112455      */
112456
112457     /**
112458      * @cfg {Boolean} multiSelect
112459      * True to enable 'MULTI' selection mode on selection model. See {@link Ext.selection.Model#mode}.
112460      */
112461
112462     /**
112463      * @cfg {Boolean} simpleSelect
112464      * True to enable 'SIMPLE' selection mode on selection model. See {@link Ext.selection.Model#mode}.
112465      */
112466
112467     /**
112468      * @cfg {Ext.data.Store} store (required)
112469      * The {@link Ext.data.Store Store} the grid should use as its data source.
112470      */
112471
112472     /**
112473      * @cfg {Number} scrollDelta
112474      * Number of pixels to scroll when scrolling with mousewheel.
112475      */
112476     scrollDelta: 40,
112477
112478     /**
112479      * @cfg {String/Boolean} scroll
112480      * Scrollers configuration. Valid values are 'both', 'horizontal' or 'vertical'.
112481      * True implies 'both'. False implies 'none'.
112482      */
112483     scroll: true,
112484
112485     /**
112486      * @cfg {Ext.grid.column.Column[]} columns
112487      * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this
112488      * grid. Each column definition provides the header text for the column, and a definition of where the data for that
112489      * column comes from.
112490      */
112491
112492     /**
112493      * @cfg {Boolean} forceFit
112494      * Ttrue to force the columns to fit into the available width. Headers are first sized according to configuration,
112495      * whether that be a specific width, or flex. Then they are all proportionally changed in width so that the entire
112496      * content width is used.
112497      */
112498
112499     /**
112500      * @cfg {Ext.grid.feature.Feature[]} features
112501      * An array of grid Features to be added to this grid. See {@link Ext.grid.feature.Feature} for usage.
112502      */
112503
112504     /**
112505      * @cfg {Boolean} [hideHeaders=false]
112506      * True to hide column headers.
112507      */
112508
112509     /**
112510      * @cfg {Boolean} deferRowRender
112511      * Defaults to true to enable deferred row rendering.
112512      *
112513      * This allows the View to execute a refresh quickly, with the expensive update of the row structure deferred so
112514      * that layouts with GridPanels appear, and lay out more quickly.
112515      */
112516
112517      deferRowRender: true,
112518      
112519     /**
112520      * @cfg {Boolean} sortableColumns
112521      * False to disable column sorting via clicking the header and via the Sorting menu items.
112522      */
112523     sortableColumns: true,
112524
112525     /**
112526      * @cfg {Boolean} [enableLocking=false]
112527      * True to enable locking support for this grid. Alternatively, locking will also be automatically
112528      * enabled if any of the columns in the column configuration contain the locked config option.
112529      */
112530     enableLocking: false,
112531
112532     verticalScrollDock: 'right',
112533     verticalScrollerType: 'gridscroller',
112534
112535     horizontalScrollerPresentCls: Ext.baseCSSPrefix + 'horizontal-scroller-present',
112536     verticalScrollerPresentCls: Ext.baseCSSPrefix + 'vertical-scroller-present',
112537
112538     // private property used to determine where to go down to find views
112539     // this is here to support locking.
112540     scrollerOwner: true,
112541
112542     invalidateScrollerOnRefresh: true,
112543
112544     /**
112545      * @cfg {Boolean} enableColumnMove
112546      * False to disable column dragging within this grid.
112547      */
112548     enableColumnMove: true,
112549
112550     /**
112551      * @cfg {Boolean} enableColumnResize
112552      * False to disable column resizing within this grid.
112553      */
112554     enableColumnResize: true,
112555
112556     /**
112557      * @cfg {Boolean} enableColumnHide
112558      * False to disable column hiding within this grid.
112559      */
112560     enableColumnHide: true,
112561
112562     initComponent: function() {
112563
112564         var me          = this,
112565             scroll      = me.scroll,
112566             vertical    = false,
112567             horizontal  = false,
112568             headerCtCfg = me.columns || me.colModel,
112569             i           = 0,
112570             view,
112571             border = me.border;
112572
112573         if (me.hideHeaders) {
112574             border = false;
112575         }
112576
112577         // Look up the configured Store. If none configured, use the fieldless, empty Store defined in Ext.data.Store.
112578         me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
112579
112580         // The columns/colModel config may be either a fully instantiated HeaderContainer, or an array of Column definitions, or a config object of a HeaderContainer
112581         // Either way, we extract a columns property referencing an array of Column definitions.
112582         if (headerCtCfg instanceof Ext.grid.header.Container) {
112583             me.headerCt = headerCtCfg;
112584             me.headerCt.border = border;
112585             me.columns = me.headerCt.items.items;
112586         } else {
112587             if (Ext.isArray(headerCtCfg)) {
112588                 headerCtCfg = {
112589                     items: headerCtCfg,
112590                     border: border
112591                 };
112592             }
112593             Ext.apply(headerCtCfg, {
112594                 forceFit: me.forceFit,
112595                 sortable: me.sortableColumns,
112596                 enableColumnMove: me.enableColumnMove,
112597                 enableColumnResize: me.enableColumnResize,
112598                 enableColumnHide: me.enableColumnHide,
112599                 border:  border
112600             });
112601             me.columns = headerCtCfg.items;
112602
112603              // If any of the Column objects contain a locked property, and are not processed, this is a lockable TablePanel, a
112604              // special view will be injected by the Ext.grid.Lockable mixin, so no processing of .
112605              if (me.enableLocking || Ext.ComponentQuery.query('{locked !== undefined}{processed != true}', me.columns).length) {
112606                  me.self.mixin('lockable', Ext.grid.Lockable);
112607                  me.injectLockable();
112608              }
112609         }
112610
112611         me.addEvents(
112612             /**
112613              * @event reconfigure
112614              * Fires after a reconfigure.
112615              * @param {Ext.panel.Table} this
112616              */
112617             'reconfigure',
112618             /**
112619              * @event viewready
112620              * Fires when the grid view is available (use this for selecting a default row).
112621              * @param {Ext.panel.Table} this
112622              */
112623             'viewready',
112624             /**
112625              * @event scrollerhide
112626              * Fires when a scroller is hidden.
112627              * @param {Ext.grid.Scroller} scroller
112628              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
112629              */
112630             'scrollerhide',
112631             /**
112632              * @event scrollershow
112633              * Fires when a scroller is shown.
112634              * @param {Ext.grid.Scroller} scroller
112635              * @param {String} orientation Orientation, can be 'vertical' or 'horizontal'
112636              */
112637             'scrollershow'
112638         );
112639
112640         me.bodyCls = me.bodyCls || '';
112641         me.bodyCls += (' ' + me.extraBodyCls);
112642         
112643         me.cls = me.cls || '';
112644         me.cls += (' ' + me.extraBaseCls);
112645
112646         // autoScroll is not a valid configuration
112647         delete me.autoScroll;
112648
112649         // If this TablePanel is lockable (Either configured lockable, or any of the defined columns has a 'locked' property)
112650         // 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.
112651         if (!me.hasView) {
112652
112653             // If we were not configured with a ready-made headerCt (either by direct config with a headerCt property, or by passing
112654             // a HeaderContainer instance as the 'columns' property, then go ahead and create one from the config object created above.
112655             if (!me.headerCt) {
112656                 me.headerCt = Ext.create('Ext.grid.header.Container', headerCtCfg);
112657             }
112658
112659             // Extract the array of Column objects
112660             me.columns = me.headerCt.items.items;
112661
112662             if (me.hideHeaders) {
112663                 me.headerCt.height = 0;
112664                 me.headerCt.border = false;
112665                 me.headerCt.addCls(Ext.baseCSSPrefix + 'grid-header-ct-hidden');
112666                 me.addCls(Ext.baseCSSPrefix + 'grid-header-hidden');
112667                 // IE Quirks Mode fix
112668                 // If hidden configuration option was used, several layout calculations will be bypassed.
112669                 if (Ext.isIEQuirks) {
112670                     me.headerCt.style = {
112671                         display: 'none'
112672                     };
112673                 }
112674             }
112675
112676             // turn both on.
112677             if (scroll === true || scroll === 'both') {
112678                 vertical = horizontal = true;
112679             } else if (scroll === 'horizontal') {
112680                 horizontal = true;
112681             } else if (scroll === 'vertical') {
112682                 vertical = true;
112683             // All other values become 'none' or false.
112684             } else {
112685                 me.headerCt.availableSpaceOffset = 0;
112686             }
112687
112688             if (vertical) {
112689                 me.verticalScroller = Ext.ComponentManager.create(me.initVerticalScroller());
112690                 me.mon(me.verticalScroller, {
112691                     bodyscroll: me.onVerticalScroll,
112692                     scope: me
112693                 });
112694             }
112695
112696             if (horizontal) {
112697                 me.horizontalScroller = Ext.ComponentManager.create(me.initHorizontalScroller());
112698                 me.mon(me.horizontalScroller, {
112699                     bodyscroll: me.onHorizontalScroll,
112700                     scope: me
112701                 });
112702             }
112703
112704             me.headerCt.on('resize', me.onHeaderResize, me);
112705             me.relayHeaderCtEvents(me.headerCt);
112706             me.features = me.features || [];
112707             if (!Ext.isArray(me.features)) {
112708                 me.features = [me.features];
112709             }
112710             me.dockedItems = me.dockedItems || [];
112711             me.dockedItems.unshift(me.headerCt);
112712             me.viewConfig = me.viewConfig || {};
112713             me.viewConfig.invalidateScrollerOnRefresh = me.invalidateScrollerOnRefresh;
112714
112715             // AbstractDataView will look up a Store configured as an object
112716             // getView converts viewConfig into a View instance
112717             view = me.getView();
112718
112719             view.on({
112720                 afterrender: function () {
112721                     // hijack the view el's scroll method
112722                     view.el.scroll = Ext.Function.bind(me.elScroll, me);
112723                     // We use to listen to document.body wheel events, but that's a
112724                     // little much. We scope just to the view now.
112725                     me.mon(view.el, {
112726                         mousewheel: me.onMouseWheel,
112727                         scope: me
112728                     });
112729                 },
112730                 single: true
112731             });
112732             me.items = [view];
112733             me.hasView = true;
112734
112735             me.mon(view.store, {
112736                 load: me.onStoreLoad,
112737                 scope: me
112738             });
112739             me.mon(view, {
112740                 viewReady: me.onViewReady,
112741                 resize: me.onViewResize,
112742                 refresh: {
112743                     fn: me.onViewRefresh,
112744                     scope: me,
112745                     buffer: 50
112746                 },
112747                 scope: me
112748             });
112749             this.relayEvents(view, [
112750                 /**
112751                  * @event beforeitemmousedown
112752                  * @alias Ext.view.View#beforeitemmousedown
112753                  */
112754                 'beforeitemmousedown',
112755                 /**
112756                  * @event beforeitemmouseup
112757                  * @alias Ext.view.View#beforeitemmouseup
112758                  */
112759                 'beforeitemmouseup',
112760                 /**
112761                  * @event beforeitemmouseenter
112762                  * @alias Ext.view.View#beforeitemmouseenter
112763                  */
112764                 'beforeitemmouseenter',
112765                 /**
112766                  * @event beforeitemmouseleave
112767                  * @alias Ext.view.View#beforeitemmouseleave
112768                  */
112769                 'beforeitemmouseleave',
112770                 /**
112771                  * @event beforeitemclick
112772                  * @alias Ext.view.View#beforeitemclick
112773                  */
112774                 'beforeitemclick',
112775                 /**
112776                  * @event beforeitemdblclick
112777                  * @alias Ext.view.View#beforeitemdblclick
112778                  */
112779                 'beforeitemdblclick',
112780                 /**
112781                  * @event beforeitemcontextmenu
112782                  * @alias Ext.view.View#beforeitemcontextmenu
112783                  */
112784                 'beforeitemcontextmenu',
112785                 /**
112786                  * @event itemmousedown
112787                  * @alias Ext.view.View#itemmousedown
112788                  */
112789                 'itemmousedown',
112790                 /**
112791                  * @event itemmouseup
112792                  * @alias Ext.view.View#itemmouseup
112793                  */
112794                 'itemmouseup',
112795                 /**
112796                  * @event itemmouseenter
112797                  * @alias Ext.view.View#itemmouseenter
112798                  */
112799                 'itemmouseenter',
112800                 /**
112801                  * @event itemmouseleave
112802                  * @alias Ext.view.View#itemmouseleave
112803                  */
112804                 'itemmouseleave',
112805                 /**
112806                  * @event itemclick
112807                  * @alias Ext.view.View#itemclick
112808                  */
112809                 'itemclick',
112810                 /**
112811                  * @event itemdblclick
112812                  * @alias Ext.view.View#itemdblclick
112813                  */
112814                 'itemdblclick',
112815                 /**
112816                  * @event itemcontextmenu
112817                  * @alias Ext.view.View#itemcontextmenu
112818                  */
112819                 'itemcontextmenu',
112820                 /**
112821                  * @event beforecontainermousedown
112822                  * @alias Ext.view.View#beforecontainermousedown
112823                  */
112824                 'beforecontainermousedown',
112825                 /**
112826                  * @event beforecontainermouseup
112827                  * @alias Ext.view.View#beforecontainermouseup
112828                  */
112829                 'beforecontainermouseup',
112830                 /**
112831                  * @event beforecontainermouseover
112832                  * @alias Ext.view.View#beforecontainermouseover
112833                  */
112834                 'beforecontainermouseover',
112835                 /**
112836                  * @event beforecontainermouseout
112837                  * @alias Ext.view.View#beforecontainermouseout
112838                  */
112839                 'beforecontainermouseout',
112840                 /**
112841                  * @event beforecontainerclick
112842                  * @alias Ext.view.View#beforecontainerclick
112843                  */
112844                 'beforecontainerclick',
112845                 /**
112846                  * @event beforecontainerdblclick
112847                  * @alias Ext.view.View#beforecontainerdblclick
112848                  */
112849                 'beforecontainerdblclick',
112850                 /**
112851                  * @event beforecontainercontextmenu
112852                  * @alias Ext.view.View#beforecontainercontextmenu
112853                  */
112854                 'beforecontainercontextmenu',
112855                 /**
112856                  * @event containermouseup
112857                  * @alias Ext.view.View#containermouseup
112858                  */
112859                 'containermouseup',
112860                 /**
112861                  * @event containermouseover
112862                  * @alias Ext.view.View#containermouseover
112863                  */
112864                 'containermouseover',
112865                 /**
112866                  * @event containermouseout
112867                  * @alias Ext.view.View#containermouseout
112868                  */
112869                 'containermouseout',
112870                 /**
112871                  * @event containerclick
112872                  * @alias Ext.view.View#containerclick
112873                  */
112874                 'containerclick',
112875                 /**
112876                  * @event containerdblclick
112877                  * @alias Ext.view.View#containerdblclick
112878                  */
112879                 'containerdblclick',
112880                 /**
112881                  * @event containercontextmenu
112882                  * @alias Ext.view.View#containercontextmenu
112883                  */
112884                 'containercontextmenu',
112885                 /**
112886                  * @event selectionchange
112887                  * @alias Ext.selection.Model#selectionchange
112888                  */
112889                 'selectionchange',
112890                 /**
112891                  * @event beforeselect
112892                  * @alias Ext.selection.RowModel#beforeselect
112893                  */
112894                 'beforeselect',
112895                 /**
112896                  * @event select
112897                  * @alias Ext.selection.RowModel#select
112898                  */
112899                 'select',
112900                 /**
112901                  * @event beforedeselect
112902                  * @alias Ext.selection.RowModel#beforedeselect
112903                  */
112904                 'beforedeselect',
112905                 /**
112906                  * @event deselect
112907                  * @alias Ext.selection.RowModel#deselect
112908                  */
112909                 'deselect'
112910             ]);
112911         }
112912
112913         me.callParent(arguments);
112914     },
112915     
112916     onRender: function(){
112917         var vScroll = this.verticalScroller,
112918             hScroll = this.horizontalScroller;
112919
112920         if (vScroll) {
112921             vScroll.ensureDimension();
112922         }
112923         if (hScroll) {
112924             hScroll.ensureDimension();
112925         }
112926         this.callParent(arguments);    
112927     },
112928
112929     // state management
112930     initStateEvents: function(){
112931         var events = this.stateEvents;
112932         // push on stateEvents if they don't exist
112933         Ext.each(['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange'], function(event){
112934             if (Ext.Array.indexOf(events, event)) {
112935                 events.push(event);
112936             }
112937         });
112938         this.callParent();
112939     },
112940
112941     /**
112942      * Returns the horizontal scroller config.
112943      */
112944     initHorizontalScroller: function () {
112945         var me = this,
112946             ret = {
112947                 xtype: 'gridscroller',
112948                 dock: 'bottom',
112949                 section: me,
112950                 store: me.store
112951             };
112952
112953         return ret;
112954     },
112955
112956     /**
112957      * Returns the vertical scroller config.
112958      */
112959     initVerticalScroller: function () {
112960         var me = this,
112961             ret = me.verticalScroller || {};
112962
112963         Ext.applyIf(ret, {
112964             xtype: me.verticalScrollerType,
112965             dock: me.verticalScrollDock,
112966             store: me.store
112967         });
112968
112969         return ret;
112970     },
112971
112972     relayHeaderCtEvents: function (headerCt) {
112973         this.relayEvents(headerCt, [
112974             /**
112975              * @event columnresize
112976              * @alias Ext.grid.header.Container#columnresize
112977              */
112978             'columnresize',
112979             /**
112980              * @event columnmove
112981              * @alias Ext.grid.header.Container#columnmove
112982              */
112983             'columnmove',
112984             /**
112985              * @event columnhide
112986              * @alias Ext.grid.header.Container#columnhide
112987              */
112988             'columnhide',
112989             /**
112990              * @event columnshow
112991              * @alias Ext.grid.header.Container#columnshow
112992              */
112993             'columnshow',
112994             /**
112995              * @event sortchange
112996              * @alias Ext.grid.header.Container#sortchange
112997              */
112998             'sortchange'
112999         ]);
113000     },
113001
113002     getState: function(){
113003         var me = this,
113004             state = me.callParent(),
113005             sorter = me.store.sorters.first();
113006
113007         state.columns = (me.headerCt || me).getColumnsState();
113008
113009         if (sorter) {
113010             state.sort = {
113011                 property: sorter.property,
113012                 direction: sorter.direction
113013             };
113014         }
113015
113016         return state;
113017     },
113018
113019     applyState: function(state) {
113020         var me = this,
113021             sorter = state.sort,
113022             store = me.store,
113023             columns = state.columns;
113024
113025         delete state.columns;
113026
113027         // Ensure superclass has applied *its* state.
113028         // AbstractComponent saves dimensions (and anchor/flex) plus collapsed state.
113029         me.callParent(arguments);
113030
113031         if (columns) {
113032             (me.headerCt || me).applyColumnsState(columns);
113033         }
113034
113035         if (sorter) {
113036             if (store.remoteSort) {
113037                 store.sorters.add(Ext.create('Ext.util.Sorter', {
113038                     property: sorter.property,
113039                     direction: sorter.direction
113040                 }));
113041             }
113042             else {
113043                 store.sort(sorter.property, sorter.direction);
113044             }
113045         }
113046     },
113047
113048     /**
113049      * Returns the store associated with this Panel.
113050      * @return {Ext.data.Store} The store
113051      */
113052     getStore: function(){
113053         return this.store;
113054     },
113055
113056     /**
113057      * Gets the view for this panel.
113058      * @return {Ext.view.Table}
113059      */
113060     getView: function() {
113061         var me = this,
113062             sm;
113063
113064         if (!me.view) {
113065             sm = me.getSelectionModel();
113066             me.view = me.createComponent(Ext.apply({}, me.viewConfig, {
113067                 deferInitialRefresh: me.deferRowRender,
113068                 xtype: me.viewType,
113069                 store: me.store,
113070                 headerCt: me.headerCt,
113071                 selModel: sm,
113072                 features: me.features,
113073                 panel: me
113074             }));
113075             me.mon(me.view, {
113076                 uievent: me.processEvent,
113077                 scope: me
113078             });
113079             sm.view = me.view;
113080             me.headerCt.view = me.view;
113081             me.relayEvents(me.view, ['cellclick', 'celldblclick']);
113082         }
113083         return me.view;
113084     },
113085
113086     /**
113087      * @private
113088      * @override
113089      * autoScroll is never valid for all classes which extend TablePanel.
113090      */
113091     setAutoScroll: Ext.emptyFn,
113092
113093     // This method hijacks Ext.view.Table's el scroll method.
113094     // This enables us to keep the virtualized scrollbars in sync
113095     // with the view. It currently does NOT support animation.
113096     elScroll: function(direction, distance, animate) {
113097         var me = this,
113098             scroller;
113099
113100         if (direction === "up" || direction === "left") {
113101             distance = -distance;
113102         }
113103         
113104         if (direction === "down" || direction === "up") {
113105             scroller = me.getVerticalScroller();
113106             
113107             //if the grid does not currently need a vertical scroller don't try to update it (EXTJSIV-3891)
113108             if (scroller) {
113109                 scroller.scrollByDeltaY(distance);
113110             }
113111         } else {
113112             scroller = me.getHorizontalScroller();
113113             
113114             //if the grid does not currently need a horizontal scroller don't try to update it (EXTJSIV-3891)
113115             if (scroller) {
113116                 scroller.scrollByDeltaX(distance);
113117             }
113118         }
113119     },
113120
113121     /**
113122      * @private
113123      * Processes UI events from the view. Propagates them to whatever internal Components need to process them.
113124      * @param {String} type Event type, eg 'click'
113125      * @param {Ext.view.Table} view TableView Component
113126      * @param {HTMLElement} cell Cell HtmlElement the event took place within
113127      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
113128      * @param {Number} cellIndex Cell index within the row
113129      * @param {Ext.EventObject} e Original event
113130      */
113131     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
113132         var me = this,
113133             header;
113134
113135         if (cellIndex !== -1) {
113136             header = me.headerCt.getGridColumns()[cellIndex];
113137             return header.processEvent.apply(header, arguments);
113138         }
113139     },
113140
113141     /**
113142      * Requests a recalculation of scrollbars and puts them in if they are needed.
113143      */
113144     determineScrollbars: function() {
113145         // Set a flag so that afterComponentLayout does not recurse back into here.
113146         if (this.determineScrollbarsRunning) {
113147             return;
113148         }
113149         this.determineScrollbarsRunning = true;
113150         var me = this,
113151             view = me.view,
113152             box,
113153             tableEl,
113154             scrollWidth,
113155             clientWidth,
113156             scrollHeight,
113157             clientHeight,
113158             verticalScroller = me.verticalScroller,
113159             horizontalScroller = me.horizontalScroller,
113160             curScrollbars = (verticalScroller   && verticalScroller.ownerCt === me ? 1 : 0) |
113161                             (horizontalScroller && horizontalScroller.ownerCt === me ? 2 : 0),
113162             reqScrollbars = 0; // 1 = vertical, 2 = horizontal, 3 = both
113163
113164         // If we are not collapsed, and the view has been rendered AND filled, then we can determine scrollbars
113165         if (!me.collapsed && view && view.viewReady) {
113166
113167             // Calculate maximum, *scrollbarless* space which the view has available.
113168             // It will be the Fit Layout's calculated size, plus the widths of any currently shown scrollbars
113169             box = view.el.getSize();
113170
113171             clientWidth  = box.width  + ((curScrollbars & 1) ? verticalScroller.width : 0);
113172             clientHeight = box.height + ((curScrollbars & 2) ? horizontalScroller.height : 0);
113173
113174             // Calculate the width of the scrolling block
113175             // There will never be a horizontal scrollbar if all columns are flexed.
113176
113177             scrollWidth = (me.headerCt.query('[flex]').length && !me.headerCt.layout.tooNarrow) ? 0 : me.headerCt.getFullWidth();
113178
113179             // Calculate the height of the scrolling block
113180             if (verticalScroller && verticalScroller.el) {
113181                 scrollHeight = verticalScroller.getSizeCalculation().height;
113182             } else {
113183                 tableEl = view.el.child('table', true);
113184                 scrollHeight = tableEl ? tableEl.offsetHeight : 0;
113185             }
113186
113187             // View is too high.
113188             // Definitely need a vertical scrollbar
113189             if (scrollHeight > clientHeight) {
113190                 reqScrollbars = 1;
113191
113192                 // But if scrollable block width goes into the zone required by the vertical scrollbar, we'll also need a horizontal
113193                 if (horizontalScroller && ((clientWidth - scrollWidth) < verticalScroller.width)) {
113194                     reqScrollbars = 3;
113195                 }
113196             }
113197
113198             // View height fits. But we stil may need a horizontal scrollbar, and this might necessitate a vertical one.
113199             else {
113200                 // View is too wide.
113201                 // Definitely need a horizontal scrollbar
113202                 if (scrollWidth > clientWidth) {
113203                     reqScrollbars = 2;
113204
113205                     // But if scrollable block height goes into the zone required by the horizontal scrollbar, we'll also need a vertical
113206                     if (verticalScroller && ((clientHeight - scrollHeight) < horizontalScroller.height)) {
113207                         reqScrollbars = 3;
113208                     }
113209                 }
113210             }
113211
113212             // If scrollbar requirements have changed, change 'em...
113213             if (reqScrollbars !== curScrollbars) {
113214
113215                 // Suspend component layout while we add/remove the docked scrollers
113216                 me.suspendLayout = true;
113217                 if (reqScrollbars & 1) {
113218                     me.showVerticalScroller();
113219                 } else {
113220                     me.hideVerticalScroller();
113221                 }
113222                 if (reqScrollbars & 2) {
113223                     me.showHorizontalScroller();
113224                 } else {
113225                     me.hideHorizontalScroller();
113226                 }
113227                 me.suspendLayout = false;
113228
113229                 // Lay out the Component.
113230                 me.doComponentLayout();
113231                 // Lay out me.items
113232                 me.getLayout().layout();
113233             }
113234         }
113235         delete me.determineScrollbarsRunning;
113236     },
113237
113238     onViewResize: function() {
113239         this.determineScrollbars();
113240     },
113241
113242     afterComponentLayout: function() {
113243         this.callParent(arguments);
113244         this.determineScrollbars();
113245         this.invalidateScroller();
113246     },
113247
113248     onHeaderResize: function() {
113249         if (!this.componentLayout.layoutBusy && this.view && this.view.rendered) {
113250             this.determineScrollbars();
113251             this.invalidateScroller();
113252         }
113253     },
113254
113255     afterCollapse: function() {
113256         var me = this;
113257         if (me.verticalScroller) {
113258             me.verticalScroller.saveScrollPos();
113259         }
113260         if (me.horizontalScroller) {
113261             me.horizontalScroller.saveScrollPos();
113262         }
113263         me.callParent(arguments);
113264     },
113265
113266     afterExpand: function() {
113267         var me = this;
113268         me.callParent(arguments);
113269         if (me.verticalScroller) {
113270             me.verticalScroller.restoreScrollPos();
113271         }
113272         if (me.horizontalScroller) {
113273             me.horizontalScroller.restoreScrollPos();
113274         }
113275     },
113276
113277     /**
113278      * Hides the verticalScroller and removes the horizontalScrollerPresentCls.
113279      */
113280     hideHorizontalScroller: function() {
113281         var me = this;
113282
113283         if (me.horizontalScroller && me.horizontalScroller.ownerCt === me) {
113284             me.verticalScroller.setReservedSpace(0);
113285             me.removeDocked(me.horizontalScroller, false);
113286             me.removeCls(me.horizontalScrollerPresentCls);
113287             me.fireEvent('scrollerhide', me.horizontalScroller, 'horizontal');
113288         }
113289
113290     },
113291
113292     /**
113293      * Shows the horizontalScroller and add the horizontalScrollerPresentCls.
113294      */
113295     showHorizontalScroller: function() {
113296         var me = this;
113297
113298         if (me.verticalScroller) {
113299             me.verticalScroller.setReservedSpace(Ext.getScrollbarSize().height - 1);
113300         }
113301         if (me.horizontalScroller && me.horizontalScroller.ownerCt !== me) {
113302             me.addDocked(me.horizontalScroller);
113303             me.addCls(me.horizontalScrollerPresentCls);
113304             me.fireEvent('scrollershow', me.horizontalScroller, 'horizontal');
113305         }
113306     },
113307
113308     /**
113309      * Hides the verticalScroller and removes the verticalScrollerPresentCls.
113310      */
113311     hideVerticalScroller: function() {
113312         var me = this;
113313
113314         me.setHeaderReserveOffset(false);
113315         if (me.verticalScroller && me.verticalScroller.ownerCt === me) {
113316             me.removeDocked(me.verticalScroller, false);
113317             me.removeCls(me.verticalScrollerPresentCls);
113318             me.fireEvent('scrollerhide', me.verticalScroller, 'vertical');
113319         }
113320     },
113321
113322     /**
113323      * Shows the verticalScroller and adds the verticalScrollerPresentCls.
113324      */
113325     showVerticalScroller: function() {
113326         var me = this;
113327
113328         me.setHeaderReserveOffset(true);
113329         if (me.verticalScroller && me.verticalScroller.ownerCt !== me) {
113330             me.addDocked(me.verticalScroller);
113331             me.addCls(me.verticalScrollerPresentCls);
113332             me.fireEvent('scrollershow', me.verticalScroller, 'vertical');
113333         }
113334     },
113335
113336     setHeaderReserveOffset: function (reserveOffset) {
113337         var headerCt = this.headerCt,
113338             layout = headerCt.layout;
113339
113340         // only trigger a layout when reserveOffset is changing
113341         if (layout && layout.reserveOffset !== reserveOffset) {
113342             layout.reserveOffset = reserveOffset;
113343             if (!this.suspendLayout) {
113344                 headerCt.doLayout();
113345             }
113346         }
113347     },
113348
113349     /**
113350      * Invalides scrollers that are present and forces a recalculation. (Not related to showing/hiding the scrollers)
113351      */
113352     invalidateScroller: function() {
113353         var me = this,
113354             vScroll = me.verticalScroller,
113355             hScroll = me.horizontalScroller;
113356
113357         if (vScroll) {
113358             vScroll.invalidate();
113359         }
113360         if (hScroll) {
113361             hScroll.invalidate();
113362         }
113363     },
113364
113365     // refresh the view when a header moves
113366     onHeaderMove: function(headerCt, header, fromIdx, toIdx) {
113367         this.view.refresh();
113368     },
113369
113370     // Section onHeaderHide is invoked after view.
113371     onHeaderHide: function(headerCt, header) {
113372         this.invalidateScroller();
113373     },
113374
113375     onHeaderShow: function(headerCt, header) {
113376         this.invalidateScroller();
113377     },
113378
113379     getVerticalScroller: function() {
113380         return this.getScrollerOwner().down('gridscroller[dock=' + this.verticalScrollDock + ']');
113381     },
113382
113383     getHorizontalScroller: function() {
113384         return this.getScrollerOwner().down('gridscroller[dock=bottom]');
113385     },
113386
113387     onMouseWheel: function(e) {
113388         var me = this,
113389             vertScroller = me.getVerticalScroller(),
113390             horizScroller = me.getHorizontalScroller(),
113391             scrollDelta = -me.scrollDelta,
113392             deltas = e.getWheelDeltas(),
113393             deltaX = scrollDelta * deltas.x,
113394             deltaY = scrollDelta * deltas.y,
113395             vertScrollerEl, horizScrollerEl,
113396             vertScrollerElDom, horizScrollerElDom,
113397             horizontalCanScrollLeft, horizontalCanScrollRight,
113398             verticalCanScrollDown, verticalCanScrollUp;
113399
113400         // calculate whether or not both scrollbars can scroll right/left and up/down
113401         if (horizScroller) {
113402             horizScrollerEl = horizScroller.scrollEl;
113403             if (horizScrollerEl) {
113404                 horizScrollerElDom = horizScrollerEl.dom;
113405                 horizontalCanScrollRight = horizScrollerElDom.scrollLeft !== horizScrollerElDom.scrollWidth - horizScrollerElDom.clientWidth;
113406                 horizontalCanScrollLeft  = horizScrollerElDom.scrollLeft !== 0;
113407             }
113408         }
113409         if (vertScroller) {
113410             vertScrollerEl = vertScroller.scrollEl;
113411             if (vertScrollerEl) {
113412                 vertScrollerElDom = vertScrollerEl.dom;
113413                 verticalCanScrollDown = vertScrollerElDom.scrollTop !== vertScrollerElDom.scrollHeight - vertScrollerElDom.clientHeight;
113414                 verticalCanScrollUp   = vertScrollerElDom.scrollTop !== 0;
113415             }
113416         }
113417
113418         if (horizScroller) {
113419             if ((deltaX < 0 && horizontalCanScrollLeft) || (deltaX > 0 && horizontalCanScrollRight)) {
113420                 e.stopEvent();
113421                 horizScroller.scrollByDeltaX(deltaX);
113422             }
113423         }
113424         if (vertScroller) {
113425             if ((deltaY < 0 && verticalCanScrollUp) || (deltaY > 0 && verticalCanScrollDown)) {
113426                 e.stopEvent();
113427                 vertScroller.scrollByDeltaY(deltaY);
113428             }
113429         }
113430     },
113431
113432     /**
113433      * @private
113434      * Fires the TablePanel's viewready event when the view declares that its internal DOM is ready
113435      */
113436     onViewReady: function() {
113437         var me = this;
113438         me.fireEvent('viewready', me);
113439         if (me.deferRowRender) {
113440             me.determineScrollbars();
113441             me.invalidateScroller();
113442         }
113443     },
113444
113445     /**
113446      * @private
113447      * Determines and invalidates scrollers on view refresh
113448      */
113449     onViewRefresh: function() {
113450         var me = this;
113451
113452         // Refresh *during* render must be ignored.
113453         if (!me.rendering) {
113454             this.determineScrollbars();
113455             if (this.invalidateScrollerOnRefresh) {
113456                 this.invalidateScroller();
113457             }
113458         }
113459     },
113460
113461     /**
113462      * Sets the scrollTop of the TablePanel.
113463      * @param {Number} top
113464      */
113465     setScrollTop: function(top) {
113466         var me               = this,
113467             rootCmp          = me.getScrollerOwner(),
113468             verticalScroller = me.getVerticalScroller();
113469
113470         rootCmp.virtualScrollTop = top;
113471         if (verticalScroller) {
113472             verticalScroller.setScrollTop(top);
113473         }
113474     },
113475
113476     getScrollerOwner: function() {
113477         var rootCmp = this;
113478         if (!this.scrollerOwner) {
113479             rootCmp = this.up('[scrollerOwner]');
113480         }
113481         return rootCmp;
113482     },
113483
113484     /**
113485      * Scrolls the TablePanel by deltaY
113486      * @param {Number} deltaY
113487      */
113488     scrollByDeltaY: function(deltaY) {
113489         var verticalScroller = this.getVerticalScroller();
113490
113491         if (verticalScroller) {
113492             verticalScroller.scrollByDeltaY(deltaY);
113493         }
113494     },
113495
113496     /**
113497      * Scrolls the TablePanel by deltaX
113498      * @param {Number} deltaX
113499      */
113500     scrollByDeltaX: function(deltaX) {
113501         var horizontalScroller = this.getHorizontalScroller();
113502
113503         if (horizontalScroller) {
113504             horizontalScroller.scrollByDeltaX(deltaX);
113505         }
113506     },
113507
113508     /**
113509      * Gets left hand side marker for header resizing.
113510      * @private
113511      */
113512     getLhsMarker: function() {
113513         var me = this;
113514
113515         if (!me.lhsMarker) {
113516             me.lhsMarker = Ext.DomHelper.append(me.el, {
113517                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
113518             }, true);
113519         }
113520         return me.lhsMarker;
113521     },
113522
113523     /**
113524      * Gets right hand side marker for header resizing.
113525      * @private
113526      */
113527     getRhsMarker: function() {
113528         var me = this;
113529
113530         if (!me.rhsMarker) {
113531             me.rhsMarker = Ext.DomHelper.append(me.el, {
113532                 cls: Ext.baseCSSPrefix + 'grid-resize-marker'
113533             }, true);
113534         }
113535         return me.rhsMarker;
113536     },
113537
113538     /**
113539      * Returns the selection model being used and creates it via the configuration if it has not been created already.
113540      * @return {Ext.selection.Model} selModel
113541      */
113542     getSelectionModel: function(){
113543         if (!this.selModel) {
113544             this.selModel = {};
113545         }
113546
113547         var mode = 'SINGLE',
113548             type;
113549         if (this.simpleSelect) {
113550             mode = 'SIMPLE';
113551         } else if (this.multiSelect) {
113552             mode = 'MULTI';
113553         }
113554
113555         Ext.applyIf(this.selModel, {
113556             allowDeselect: this.allowDeselect,
113557             mode: mode
113558         });
113559
113560         if (!this.selModel.events) {
113561             type = this.selModel.selType || this.selType;
113562             this.selModel = Ext.create('selection.' + type, this.selModel);
113563         }
113564
113565         if (!this.selModel.hasRelaySetup) {
113566             this.relayEvents(this.selModel, [
113567                 'selectionchange', 'beforeselect', 'beforedeselect', 'select', 'deselect'
113568             ]);
113569             this.selModel.hasRelaySetup = true;
113570         }
113571
113572         // lock the selection model if user
113573         // has disabled selection
113574         if (this.disableSelection) {
113575             this.selModel.locked = true;
113576         }
113577         return this.selModel;
113578     },
113579
113580     onVerticalScroll: function(event, target) {
113581         var owner = this.getScrollerOwner(),
113582             items = owner.query('tableview'),
113583             i = 0,
113584             len = items.length;
113585
113586         for (; i < len; i++) {
113587             items[i].el.dom.scrollTop = target.scrollTop;
113588         }
113589     },
113590
113591     onHorizontalScroll: function(event, target) {
113592         var owner = this.getScrollerOwner(),
113593             items = owner.query('tableview'),
113594             center = items[1] || items[0];
113595
113596         center.el.dom.scrollLeft = target.scrollLeft;
113597         this.headerCt.el.dom.scrollLeft = target.scrollLeft;
113598     },
113599
113600     // template method meant to be overriden
113601     onStoreLoad: Ext.emptyFn,
113602
113603     getEditorParent: function() {
113604         return this.body;
113605     },
113606
113607     bindStore: function(store) {
113608         var me = this;
113609         me.store = store;
113610         me.getView().bindStore(store);
113611     },
113612     
113613     beforeDestroy: function(){
113614         // may be some duplication here since the horizontal and vertical
113615         // scroller may be part of the docked items, but we need to clean
113616         // them up in case they aren't visible.
113617         Ext.destroy(this.horizontalScroller, this.verticalScroller);
113618         this.callParent();
113619     },
113620
113621     /**
113622      * Reconfigures the table with a new store/columns. Either the store or the columns can be ommitted if you don't wish
113623      * to change them.
113624      * @param {Ext.data.Store} store (Optional) The new store.
113625      * @param {Object[]} columns (Optional) An array of column configs
113626      */
113627     reconfigure: function(store, columns) {
113628         var me = this,
113629             headerCt = me.headerCt;
113630
113631         if (me.lockable) {
113632             me.reconfigureLockable(store, columns);
113633         } else {
113634             if (columns) {
113635                 headerCt.suspendLayout = true;
113636                 headerCt.removeAll();
113637                 headerCt.add(columns);
113638             }
113639             if (store) {
113640                 store = Ext.StoreManager.lookup(store);
113641                 me.bindStore(store);
113642             } else {
113643                 me.getView().refresh();
113644             }
113645             if (columns) {
113646                 headerCt.suspendLayout = false;
113647                 me.forceComponentLayout();
113648             }
113649         }
113650         me.fireEvent('reconfigure', me);
113651     }
113652 });
113653 /**
113654  * This class encapsulates the user interface for a tabular data set.
113655  * It acts as a centralized manager for controlling the various interface
113656  * elements of the view. This includes handling events, such as row and cell
113657  * level based DOM events. It also reacts to events from the underlying {@link Ext.selection.Model}
113658  * to provide visual feedback to the user.
113659  *
113660  * This class does not provide ways to manipulate the underlying data of the configured
113661  * {@link Ext.data.Store}.
113662  *
113663  * This is the base class for both {@link Ext.grid.View} and {@link Ext.tree.View} and is not
113664  * to be used directly.
113665  */
113666 Ext.define('Ext.view.Table', {
113667     extend: 'Ext.view.View',
113668     alias: 'widget.tableview',
113669     uses: [
113670         'Ext.view.TableChunker',
113671         'Ext.util.DelayedTask',
113672         'Ext.util.MixedCollection'
113673     ],
113674
113675     baseCls: Ext.baseCSSPrefix + 'grid-view',
113676
113677     // row
113678     itemSelector: '.' + Ext.baseCSSPrefix + 'grid-row',
113679     // cell
113680     cellSelector: '.' + Ext.baseCSSPrefix + 'grid-cell',
113681
113682     selectedItemCls: Ext.baseCSSPrefix + 'grid-row-selected',
113683     selectedCellCls: Ext.baseCSSPrefix + 'grid-cell-selected',
113684     focusedItemCls: Ext.baseCSSPrefix + 'grid-row-focused',
113685     overItemCls: Ext.baseCSSPrefix + 'grid-row-over',
113686     altRowCls:   Ext.baseCSSPrefix + 'grid-row-alt',
113687     rowClsRe: /(?:^|\s*)grid-row-(first|last|alt)(?:\s+|$)/g,
113688     cellRe: new RegExp('x-grid-cell-([^\\s]+) ', ''),
113689
113690     // cfg docs inherited
113691     trackOver: true,
113692
113693     /**
113694      * Override this function to apply custom CSS classes to rows during rendering. This function should return the
113695      * CSS class name (or empty string '' for none) that will be added to the row's wrapping div. To apply multiple
113696      * class names, simply return them space-delimited within the string (e.g. 'my-class another-class').
113697      * Example usage:
113698      *
113699      *     viewConfig: {
113700      *         getRowClass: function(record, rowIndex, rowParams, store){
113701      *             return record.get("valid") ? "row-valid" : "row-error";
113702      *         }
113703      *     }
113704      *
113705      * @param {Ext.data.Model} record The record corresponding to the current row.
113706      * @param {Number} index The row index.
113707      * @param {Object} rowParams **DEPRECATED.** For row body use the
113708      * {@link Ext.grid.feature.RowBody#getAdditionalData getAdditionalData} method of the rowbody feature.
113709      * @param {Ext.data.Store} store The store this grid is bound to
113710      * @return {String} a CSS class name to add to the row.
113711      * @method
113712      */
113713     getRowClass: null,
113714
113715     initComponent: function() {
113716         var me = this;
113717
113718         me.scrollState = {};
113719         me.selModel.view = me;
113720         me.headerCt.view = me;
113721         me.initFeatures();
113722         me.tpl = '<div></div>';
113723         me.callParent();
113724         me.mon(me.store, {
113725             load: me.onStoreLoad,
113726             scope: me
113727         });
113728
113729         // this.addEvents(
113730         //     /**
113731         //      * @event rowfocus
113732         //      * @param {Ext.data.Model} record
113733         //      * @param {HTMLElement} row
113734         //      * @param {Number} rowIdx
113735         //      */
113736         //     'rowfocus'
113737         // );
113738     },
113739
113740     // scroll to top of the grid when store loads
113741     onStoreLoad: function(){
113742         var me = this;
113743
113744         if (me.invalidateScrollerOnRefresh) {
113745             if (Ext.isGecko) {
113746                 if (!me.scrollToTopTask) {
113747                     me.scrollToTopTask = Ext.create('Ext.util.DelayedTask', me.scrollToTop, me);
113748                 }
113749                 me.scrollToTopTask.delay(1);
113750             } else {
113751                 me    .scrollToTop();
113752             }
113753         }
113754     },
113755
113756     // scroll the view to the top
113757     scrollToTop: Ext.emptyFn,
113758
113759     /**
113760      * Add a listener to the main view element. It will be destroyed with the view.
113761      * @private
113762      */
113763     addElListener: function(eventName, fn, scope){
113764         this.mon(this, eventName, fn, scope, {
113765             element: 'el'
113766         });
113767     },
113768
113769     /**
113770      * Get the columns used for generating a template via TableChunker.
113771      * See {@link Ext.grid.header.Container#getGridColumns}.
113772      * @private
113773      */
113774     getGridColumns: function() {
113775         return this.headerCt.getGridColumns();
113776     },
113777
113778     /**
113779      * Get a leaf level header by index regardless of what the nesting
113780      * structure is.
113781      * @private
113782      * @param {Number} index The index
113783      */
113784     getHeaderAtIndex: function(index) {
113785         return this.headerCt.getHeaderAtIndex(index);
113786     },
113787
113788     /**
113789      * Get the cell (td) for a particular record and column.
113790      * @param {Ext.data.Model} record
113791      * @param {Ext.grid.column.Column} column
113792      * @private
113793      */
113794     getCell: function(record, column) {
113795         var row = this.getNode(record);
113796         return Ext.fly(row).down(column.getCellSelector());
113797     },
113798
113799     /**
113800      * Get a reference to a feature
113801      * @param {String} id The id of the feature
113802      * @return {Ext.grid.feature.Feature} The feature. Undefined if not found
113803      */
113804     getFeature: function(id) {
113805         var features = this.featuresMC;
113806         if (features) {
113807             return features.get(id);
113808         }
113809     },
113810
113811     /**
113812      * Initializes each feature and bind it to this view.
113813      * @private
113814      */
113815     initFeatures: function() {
113816         var me = this,
113817             i = 0,
113818             features,
113819             len;
113820
113821         me.features = me.features || [];
113822         features = me.features;
113823         len = features.length;
113824
113825         me.featuresMC = Ext.create('Ext.util.MixedCollection');
113826         for (; i < len; i++) {
113827             // ensure feature hasnt already been instantiated
113828             if (!features[i].isFeature) {
113829                 features[i] = Ext.create('feature.' + features[i].ftype, features[i]);
113830             }
113831             // inject a reference to view
113832             features[i].view = me;
113833             me.featuresMC.add(features[i]);
113834         }
113835     },
113836
113837     /**
113838      * Gives features an injection point to attach events to the markup that
113839      * has been created for this view.
113840      * @private
113841      */
113842     attachEventsForFeatures: function() {
113843         var features = this.features,
113844             ln       = features.length,
113845             i        = 0;
113846
113847         for (; i < ln; i++) {
113848             if (features[i].isFeature) {
113849                 features[i].attachEvents();
113850             }
113851         }
113852     },
113853
113854     afterRender: function() {
113855         var me = this;
113856
113857         me.callParent();
113858         me.mon(me.el, {
113859             scroll: me.fireBodyScroll,
113860             scope: me
113861         });
113862         me.el.unselectable();
113863         me.attachEventsForFeatures();
113864     },
113865
113866     fireBodyScroll: function(e, t) {
113867         this.fireEvent('bodyscroll', e, t);
113868     },
113869
113870     // TODO: Refactor headerCt dependency here to colModel
113871     /**
113872      * Uses the headerCt to transform data from dataIndex keys in a record to
113873      * headerId keys in each header and then run them through each feature to
113874      * get additional data for variables they have injected into the view template.
113875      * @private
113876      */
113877     prepareData: function(data, idx, record) {
113878         var me       = this,
113879             orig     = me.headerCt.prepareData(data, idx, record, me, me.ownerCt),
113880             features = me.features,
113881             ln       = features.length,
113882             i        = 0,
113883             node, feature;
113884
113885         for (; i < ln; i++) {
113886             feature = features[i];
113887             if (feature.isFeature) {
113888                 Ext.apply(orig, feature.getAdditionalData(data, idx, record, orig, me));
113889             }
113890         }
113891
113892         return orig;
113893     },
113894
113895     // TODO: Refactor headerCt dependency here to colModel
113896     collectData: function(records, startIndex) {
113897         var preppedRecords = this.callParent(arguments),
113898             headerCt  = this.headerCt,
113899             fullWidth = headerCt.getFullWidth(),
113900             features  = this.features,
113901             ln = features.length,
113902             o = {
113903                 rows: preppedRecords,
113904                 fullWidth: fullWidth
113905             },
113906             i  = 0,
113907             feature,
113908             j = 0,
113909             jln,
113910             rowParams;
113911
113912         jln = preppedRecords.length;
113913         // process row classes, rowParams has been deprecated and has been moved
113914         // to the individual features that implement the behavior.
113915         if (this.getRowClass) {
113916             for (; j < jln; j++) {
113917                 rowParams = {};
113918                 preppedRecords[j]['rowCls'] = this.getRowClass(records[j], j, rowParams, this.store);
113919             }
113920         }
113921         // currently only one feature may implement collectData. This is to modify
113922         // what's returned to the view before its rendered
113923         for (; i < ln; i++) {
113924             feature = features[i];
113925             if (feature.isFeature && feature.collectData && !feature.disabled) {
113926                 o = feature.collectData(records, preppedRecords, startIndex, fullWidth, o);
113927                 break;
113928             }
113929         }
113930         return o;
113931     },
113932
113933     // TODO: Refactor header resizing to column resizing
113934     /**
113935      * When a header is resized, setWidth on the individual columns resizer class,
113936      * the top level table, save/restore scroll state, generate a new template and
113937      * restore focus to the grid view's element so that keyboard navigation
113938      * continues to work.
113939      * @private
113940      */
113941     onHeaderResize: function(header, w, suppressFocus) {
113942         var me = this,
113943             el = me.el;
113944
113945         if (el) {
113946             me.saveScrollState();
113947             // Grab the col and set the width, css
113948             // class is generated in TableChunker.
113949             // Select composites because there may be several chunks.
113950
113951             // IE6 and IE7 bug.
113952             // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
113953             // We need to increment the passed with in this case.
113954             if (Ext.isIE6 || Ext.isIE7) {
113955                 if (header.el.hasCls(Ext.baseCSSPrefix + 'column-header-first')) {
113956                     w += 1;
113957                 }
113958             }
113959             el.select('.' + Ext.baseCSSPrefix + 'grid-col-resizer-'+header.id).setWidth(w);
113960             el.select('.' + Ext.baseCSSPrefix + 'grid-table-resizer').setWidth(me.headerCt.getFullWidth());
113961             me.restoreScrollState();
113962             if (!me.ignoreTemplate) {
113963                 me.setNewTemplate();
113964             }
113965             if (!suppressFocus) {
113966                 me.el.focus();
113967             }
113968         }
113969     },
113970
113971     /**
113972      * When a header is shown restore its oldWidth if it was previously hidden.
113973      * @private
113974      */
113975     onHeaderShow: function(headerCt, header, suppressFocus) {
113976         var me = this;
113977         me.ignoreTemplate = true;
113978         // restore headers that were dynamically hidden
113979         if (header.oldWidth) {
113980             me.onHeaderResize(header, header.oldWidth, suppressFocus);
113981             delete header.oldWidth;
113982         // flexed headers will have a calculated size set
113983         // this additional check has to do with the fact that
113984         // defaults: {width: 100} will fight with a flex value
113985         } else if (header.width && !header.flex) {
113986             me.onHeaderResize(header, header.width, suppressFocus);
113987         }
113988         delete me.ignoreTemplate;
113989         me.setNewTemplate();
113990     },
113991
113992     /**
113993      * When the header hides treat it as a resize to 0.
113994      * @private
113995      */
113996     onHeaderHide: function(headerCt, header, suppressFocus) {
113997         this.onHeaderResize(header, 0, suppressFocus);
113998     },
113999
114000     /**
114001      * Set a new template based on the current columns displayed in the
114002      * grid.
114003      * @private
114004      */
114005     setNewTemplate: function() {
114006         var me = this,
114007             columns = me.headerCt.getColumnsForTpl(true);
114008
114009         me.tpl = me.getTableChunker().getTableTpl({
114010             columns: columns,
114011             features: me.features
114012         });
114013     },
114014
114015     /**
114016      * Returns the configured chunker or default of Ext.view.TableChunker
114017      */
114018     getTableChunker: function() {
114019         return this.chunker || Ext.view.TableChunker;
114020     },
114021
114022     /**
114023      * Adds a CSS Class to a specific row.
114024      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model
114025      * representing this row
114026      * @param {String} cls
114027      */
114028     addRowCls: function(rowInfo, cls) {
114029         var row = this.getNode(rowInfo);
114030         if (row) {
114031             Ext.fly(row).addCls(cls);
114032         }
114033     },
114034
114035     /**
114036      * Removes a CSS Class from a specific row.
114037      * @param {HTMLElement/String/Number/Ext.data.Model} rowInfo An HTMLElement, index or instance of a model
114038      * representing this row
114039      * @param {String} cls
114040      */
114041     removeRowCls: function(rowInfo, cls) {
114042         var row = this.getNode(rowInfo);
114043         if (row) {
114044             Ext.fly(row).removeCls(cls);
114045         }
114046     },
114047
114048     // GridSelectionModel invokes onRowSelect as selection changes
114049     onRowSelect : function(rowIdx) {
114050         this.addRowCls(rowIdx, this.selectedItemCls);
114051     },
114052
114053     // GridSelectionModel invokes onRowDeselect as selection changes
114054     onRowDeselect : function(rowIdx) {
114055         var me = this;
114056
114057         me.removeRowCls(rowIdx, me.selectedItemCls);
114058         me.removeRowCls(rowIdx, me.focusedItemCls);
114059     },
114060
114061     onCellSelect: function(position) {
114062         var cell = this.getCellByPosition(position);
114063         if (cell) {
114064             cell.addCls(this.selectedCellCls);
114065         }
114066     },
114067
114068     onCellDeselect: function(position) {
114069         var cell = this.getCellByPosition(position);
114070         if (cell) {
114071             cell.removeCls(this.selectedCellCls);
114072         }
114073
114074     },
114075
114076     onCellFocus: function(position) {
114077         //var cell = this.getCellByPosition(position);
114078         this.focusCell(position);
114079     },
114080
114081     getCellByPosition: function(position) {
114082         var row    = position.row,
114083             column = position.column,
114084             store  = this.store,
114085             node   = this.getNode(row),
114086             header = this.headerCt.getHeaderAtIndex(column),
114087             cellSelector,
114088             cell = false;
114089
114090         if (header && node) {
114091             cellSelector = header.getCellSelector();
114092             cell = Ext.fly(node).down(cellSelector);
114093         }
114094         return cell;
114095     },
114096
114097     // GridSelectionModel invokes onRowFocus to 'highlight'
114098     // the last row focused
114099     onRowFocus: function(rowIdx, highlight, supressFocus) {
114100         var me = this,
114101             row = me.getNode(rowIdx);
114102
114103         if (highlight) {
114104             me.addRowCls(rowIdx, me.focusedItemCls);
114105             if (!supressFocus) {
114106                 me.focusRow(rowIdx);
114107             }
114108             //this.el.dom.setAttribute('aria-activedescendant', row.id);
114109         } else {
114110             me.removeRowCls(rowIdx, me.focusedItemCls);
114111         }
114112     },
114113
114114     /**
114115      * Focuses a particular row and brings it into view. Will fire the rowfocus event.
114116      * @param {HTMLElement/String/Number/Ext.data.Model} rowIdx
114117      * An HTMLElement template node, index of a template node, the id of a template node or the
114118      * record associated with the node.
114119      */
114120     focusRow: function(rowIdx) {
114121         var me         = this,
114122             row        = me.getNode(rowIdx),
114123             el         = me.el,
114124             adjustment = 0,
114125             panel      = me.ownerCt,
114126             rowRegion,
114127             elRegion,
114128             record;
114129
114130         if (row && el) {
114131             elRegion  = el.getRegion();
114132             rowRegion = Ext.fly(row).getRegion();
114133             // row is above
114134             if (rowRegion.top < elRegion.top) {
114135                 adjustment = rowRegion.top - elRegion.top;
114136             // row is below
114137             } else if (rowRegion.bottom > elRegion.bottom) {
114138                 adjustment = rowRegion.bottom - elRegion.bottom;
114139             }
114140             record = me.getRecord(row);
114141             rowIdx = me.store.indexOf(record);
114142
114143             if (adjustment) {
114144                 // scroll the grid itself, so that all gridview's update.
114145                 panel.scrollByDeltaY(adjustment);
114146             }
114147             me.fireEvent('rowfocus', record, row, rowIdx);
114148         }
114149     },
114150
114151     focusCell: function(position) {
114152         var me          = this,
114153             cell        = me.getCellByPosition(position),
114154             el          = me.el,
114155             adjustmentY = 0,
114156             adjustmentX = 0,
114157             elRegion    = el.getRegion(),
114158             panel       = me.ownerCt,
114159             cellRegion,
114160             record;
114161
114162         if (cell) {
114163             cellRegion = cell.getRegion();
114164             // cell is above
114165             if (cellRegion.top < elRegion.top) {
114166                 adjustmentY = cellRegion.top - elRegion.top;
114167             // cell is below
114168             } else if (cellRegion.bottom > elRegion.bottom) {
114169                 adjustmentY = cellRegion.bottom - elRegion.bottom;
114170             }
114171
114172             // cell is left
114173             if (cellRegion.left < elRegion.left) {
114174                 adjustmentX = cellRegion.left - elRegion.left;
114175             // cell is right
114176             } else if (cellRegion.right > elRegion.right) {
114177                 adjustmentX = cellRegion.right - elRegion.right;
114178             }
114179
114180             if (adjustmentY) {
114181                 // scroll the grid itself, so that all gridview's update.
114182                 panel.scrollByDeltaY(adjustmentY);
114183             }
114184             if (adjustmentX) {
114185                 panel.scrollByDeltaX(adjustmentX);
114186             }
114187             el.focus();
114188             me.fireEvent('cellfocus', record, cell, position);
114189         }
114190     },
114191
114192     /**
114193      * Scrolls by delta. This affects this individual view ONLY and does not
114194      * synchronize across views or scrollers.
114195      * @param {Number} delta
114196      * @param {String} dir (optional) Valid values are scrollTop and scrollLeft. Defaults to scrollTop.
114197      * @private
114198      */
114199     scrollByDelta: function(delta, dir) {
114200         dir = dir || 'scrollTop';
114201         var elDom = this.el.dom;
114202         elDom[dir] = (elDom[dir] += delta);
114203     },
114204
114205     onUpdate: function(ds, index) {
114206         this.callParent(arguments);
114207     },
114208
114209     /**
114210      * Saves the scrollState in a private variable. Must be used in conjunction with restoreScrollState
114211      */
114212     saveScrollState: function() {
114213         if (this.rendered) {
114214             var dom = this.el.dom, 
114215                 state = this.scrollState;
114216             
114217             state.left = dom.scrollLeft;
114218             state.top = dom.scrollTop;
114219         }
114220     },
114221
114222     /**
114223      * Restores the scrollState.
114224      * Must be used in conjunction with saveScrollState
114225      * @private
114226      */
114227     restoreScrollState: function() {
114228         if (this.rendered) {
114229             var dom = this.el.dom, 
114230                 state = this.scrollState, 
114231                 headerEl = this.headerCt.el.dom;
114232             
114233             headerEl.scrollLeft = dom.scrollLeft = state.left;
114234             dom.scrollTop = state.top;
114235         }
114236     },
114237
114238     /**
114239      * Refreshes the grid view. Saves and restores the scroll state, generates a new template, stripes rows and
114240      * invalidates the scrollers.
114241      */
114242     refresh: function() {
114243         this.setNewTemplate();
114244         this.callParent(arguments);
114245     },
114246
114247     processItemEvent: function(record, row, rowIndex, e) {
114248         var me = this,
114249             cell = e.getTarget(me.cellSelector, row),
114250             cellIndex = cell ? cell.cellIndex : -1,
114251             map = me.statics().EventMap,
114252             selModel = me.getSelectionModel(),
114253             type = e.type,
114254             result;
114255
114256         if (type == 'keydown' && !cell && selModel.getCurrentPosition) {
114257             // CellModel, otherwise we can't tell which cell to invoke
114258             cell = me.getCellByPosition(selModel.getCurrentPosition());
114259             if (cell) {
114260                 cell = cell.dom;
114261                 cellIndex = cell.cellIndex;
114262             }
114263         }
114264
114265         result = me.fireEvent('uievent', type, me, cell, rowIndex, cellIndex, e);
114266
114267         if (result === false || me.callParent(arguments) === false) {
114268             return false;
114269         }
114270
114271         // Don't handle cellmouseenter and cellmouseleave events for now
114272         if (type == 'mouseover' || type == 'mouseout') {
114273             return true;
114274         }
114275
114276         return !(
114277             // We are adding cell and feature events
114278             (me['onBeforeCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
114279             (me.fireEvent('beforecell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false) ||
114280             (me['onCell' + map[type]](cell, cellIndex, record, row, rowIndex, e) === false) ||
114281             (me.fireEvent('cell' + type, me, cell, cellIndex, record, row, rowIndex, e) === false)
114282         );
114283     },
114284
114285     processSpecialEvent: function(e) {
114286         var me = this,
114287             map = me.statics().EventMap,
114288             features = me.features,
114289             ln = features.length,
114290             type = e.type,
114291             i, feature, prefix, featureTarget,
114292             beforeArgs, args,
114293             panel = me.ownerCt;
114294
114295         me.callParent(arguments);
114296
114297         if (type == 'mouseover' || type == 'mouseout') {
114298             return;
114299         }
114300
114301         for (i = 0; i < ln; i++) {
114302             feature = features[i];
114303             if (feature.hasFeatureEvent) {
114304                 featureTarget = e.getTarget(feature.eventSelector, me.getTargetEl());
114305                 if (featureTarget) {
114306                     prefix = feature.eventPrefix;
114307                     // allows features to implement getFireEventArgs to change the
114308                     // fireEvent signature
114309                     beforeArgs = feature.getFireEventArgs('before' + prefix + type, me, featureTarget, e);
114310                     args = feature.getFireEventArgs(prefix + type, me, featureTarget, e);
114311
114312                     if (
114313                         // before view event
114314                         (me.fireEvent.apply(me, beforeArgs) === false) ||
114315                         // panel grid event
114316                         (panel.fireEvent.apply(panel, beforeArgs) === false) ||
114317                         // view event
114318                         (me.fireEvent.apply(me, args) === false) ||
114319                         // panel event
114320                         (panel.fireEvent.apply(panel, args) === false)
114321                     ) {
114322                         return false;
114323                     }
114324                 }
114325             }
114326         }
114327         return true;
114328     },
114329
114330     onCellMouseDown: Ext.emptyFn,
114331     onCellMouseUp: Ext.emptyFn,
114332     onCellClick: Ext.emptyFn,
114333     onCellDblClick: Ext.emptyFn,
114334     onCellContextMenu: Ext.emptyFn,
114335     onCellKeyDown: Ext.emptyFn,
114336     onBeforeCellMouseDown: Ext.emptyFn,
114337     onBeforeCellMouseUp: Ext.emptyFn,
114338     onBeforeCellClick: Ext.emptyFn,
114339     onBeforeCellDblClick: Ext.emptyFn,
114340     onBeforeCellContextMenu: Ext.emptyFn,
114341     onBeforeCellKeyDown: Ext.emptyFn,
114342
114343     /**
114344      * Expands a particular header to fit the max content width.
114345      * This will ONLY expand, not contract.
114346      * @private
114347      */
114348     expandToFit: function(header) {
114349         if (header) {
114350             var maxWidth = this.getMaxContentWidth(header);
114351             delete header.flex;
114352             header.setWidth(maxWidth);
114353         }
114354     },
114355
114356     /**
114357      * Returns the max contentWidth of the header's text and all cells
114358      * in the grid under this header.
114359      * @private
114360      */
114361     getMaxContentWidth: function(header) {
114362         var cellSelector = header.getCellInnerSelector(),
114363             cells        = this.el.query(cellSelector),
114364             i = 0,
114365             ln = cells.length,
114366             maxWidth = header.el.dom.scrollWidth,
114367             scrollWidth;
114368
114369         for (; i < ln; i++) {
114370             scrollWidth = cells[i].scrollWidth;
114371             if (scrollWidth > maxWidth) {
114372                 maxWidth = scrollWidth;
114373             }
114374         }
114375         return maxWidth;
114376     },
114377
114378     getPositionByEvent: function(e) {
114379         var me       = this,
114380             cellNode = e.getTarget(me.cellSelector),
114381             rowNode  = e.getTarget(me.itemSelector),
114382             record   = me.getRecord(rowNode),
114383             header   = me.getHeaderByCell(cellNode);
114384
114385         return me.getPosition(record, header);
114386     },
114387
114388     getHeaderByCell: function(cell) {
114389         if (cell) {
114390             var m = cell.className.match(this.cellRe);
114391             if (m && m[1]) {
114392                 return Ext.getCmp(m[1]);
114393             }
114394         }
114395         return false;
114396     },
114397
114398     /**
114399      * @param {Object} position The current row and column: an object containing the following properties:
114400      *
114401      * - row - The row index
114402      * - column - The column index
114403      *
114404      * @param {String} direction 'up', 'down', 'right' and 'left'
114405      * @param {Ext.EventObject} e event
114406      * @param {Boolean} preventWrap Set to true to prevent wrap around to the next or previous row.
114407      * @param {Function} verifierFn A function to verify the validity of the calculated position.
114408      * When using this function, you must return true to allow the newPosition to be returned.
114409      * @param {Object} scope Scope to run the verifierFn in
114410      * @returns {Object} newPosition An object containing the following properties:
114411      *
114412      * - row - The row index
114413      * - column - The column index
114414      *
114415      * @private
114416      */
114417     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
114418         var me       = this,
114419             row      = pos.row,
114420             column   = pos.column,
114421             rowCount = me.store.getCount(),
114422             firstCol = me.getFirstVisibleColumnIndex(),
114423             lastCol  = me.getLastVisibleColumnIndex(),
114424             newPos   = {row: row, column: column},
114425             activeHeader = me.headerCt.getHeaderAtIndex(column);
114426
114427         // no active header or its currently hidden
114428         if (!activeHeader || activeHeader.hidden) {
114429             return false;
114430         }
114431
114432         e = e || {};
114433         direction = direction.toLowerCase();
114434         switch (direction) {
114435             case 'right':
114436                 // has the potential to wrap if its last
114437                 if (column === lastCol) {
114438                     // if bottom row and last column, deny right
114439                     if (preventWrap || row === rowCount - 1) {
114440                         return false;
114441                     }
114442                     if (!e.ctrlKey) {
114443                         // otherwise wrap to nextRow and firstCol
114444                         newPos.row = row + 1;
114445                         newPos.column = firstCol;
114446                     }
114447                 // go right
114448                 } else {
114449                     if (!e.ctrlKey) {
114450                         newPos.column = column + me.getRightGap(activeHeader);
114451                     } else {
114452                         newPos.column = lastCol;
114453                     }
114454                 }
114455                 break;
114456
114457             case 'left':
114458                 // has the potential to wrap
114459                 if (column === firstCol) {
114460                     // if top row and first column, deny left
114461                     if (preventWrap || row === 0) {
114462                         return false;
114463                     }
114464                     if (!e.ctrlKey) {
114465                         // otherwise wrap to prevRow and lastCol
114466                         newPos.row = row - 1;
114467                         newPos.column = lastCol;
114468                     }
114469                 // go left
114470                 } else {
114471                     if (!e.ctrlKey) {
114472                         newPos.column = column + me.getLeftGap(activeHeader);
114473                     } else {
114474                         newPos.column = firstCol;
114475                     }
114476                 }
114477                 break;
114478
114479             case 'up':
114480                 // if top row, deny up
114481                 if (row === 0) {
114482                     return false;
114483                 // go up
114484                 } else {
114485                     if (!e.ctrlKey) {
114486                         newPos.row = row - 1;
114487                     } else {
114488                         newPos.row = 0;
114489                     }
114490                 }
114491                 break;
114492
114493             case 'down':
114494                 // if bottom row, deny down
114495                 if (row === rowCount - 1) {
114496                     return false;
114497                 // go down
114498                 } else {
114499                     if (!e.ctrlKey) {
114500                         newPos.row = row + 1;
114501                     } else {
114502                         newPos.row = rowCount - 1;
114503                     }
114504                 }
114505                 break;
114506         }
114507
114508         if (verifierFn && verifierFn.call(scope || window, newPos) !== true) {
114509             return false;
114510         } else {
114511             return newPos;
114512         }
114513     },
114514     getFirstVisibleColumnIndex: function() {
114515         var headerCt   = this.getHeaderCt(),
114516             allColumns = headerCt.getGridColumns(),
114517             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
114518             firstHeader = visHeaders[0];
114519
114520         return headerCt.getHeaderIndex(firstHeader);
114521     },
114522
114523     getLastVisibleColumnIndex: function() {
114524         var headerCt   = this.getHeaderCt(),
114525             allColumns = headerCt.getGridColumns(),
114526             visHeaders = Ext.ComponentQuery.query(':not([hidden])', allColumns),
114527             lastHeader = visHeaders[visHeaders.length - 1];
114528
114529         return headerCt.getHeaderIndex(lastHeader);
114530     },
114531
114532     getHeaderCt: function() {
114533         return this.headerCt;
114534     },
114535
114536     getPosition: function(record, header) {
114537         var me = this,
114538             store = me.store,
114539             gridCols = me.headerCt.getGridColumns();
114540
114541         return {
114542             row: store.indexOf(record),
114543             column: Ext.Array.indexOf(gridCols, header)
114544         };
114545     },
114546
114547     /**
114548      * Determines the 'gap' between the closest adjacent header to the right
114549      * that is not hidden.
114550      * @private
114551      */
114552     getRightGap: function(activeHeader) {
114553         var headerCt        = this.getHeaderCt(),
114554             headers         = headerCt.getGridColumns(),
114555             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
114556             i               = activeHeaderIdx + 1,
114557             nextIdx;
114558
114559         for (; i <= headers.length; i++) {
114560             if (!headers[i].hidden) {
114561                 nextIdx = i;
114562                 break;
114563             }
114564         }
114565
114566         return nextIdx - activeHeaderIdx;
114567     },
114568
114569     beforeDestroy: function() {
114570         if (this.rendered) {
114571             this.el.removeAllListeners();
114572         }
114573         this.callParent(arguments);
114574     },
114575
114576     /**
114577      * Determines the 'gap' between the closest adjacent header to the left
114578      * that is not hidden.
114579      * @private
114580      */
114581     getLeftGap: function(activeHeader) {
114582         var headerCt        = this.getHeaderCt(),
114583             headers         = headerCt.getGridColumns(),
114584             activeHeaderIdx = Ext.Array.indexOf(headers, activeHeader),
114585             i               = activeHeaderIdx - 1,
114586             prevIdx;
114587
114588         for (; i >= 0; i--) {
114589             if (!headers[i].hidden) {
114590                 prevIdx = i;
114591                 break;
114592             }
114593         }
114594
114595         return prevIdx - activeHeaderIdx;
114596     }
114597 });
114598 /**
114599  * @class Ext.grid.View
114600  * @extends Ext.view.Table
114601  *
114602  * The grid View class provides extra {@link Ext.grid.Panel} specific functionality to the
114603  * {@link Ext.view.Table}. In general, this class is not instanced directly, instead a viewConfig
114604  * option is passed to the grid:
114605  *
114606  *     Ext.create('Ext.grid.Panel', {
114607  *         // other options
114608  *         viewConfig: {
114609  *             stripeRows: false
114610  *         }
114611  *     });
114612  *
114613  * ## Drag Drop
114614  *
114615  * Drag and drop functionality can be achieved in the grid by attaching a {@link Ext.grid.plugin.DragDrop} plugin
114616  * when creating the view.
114617  *
114618  *     Ext.create('Ext.grid.Panel', {
114619  *         // other options
114620  *         viewConfig: {
114621  *             plugins: {
114622  *                 ddGroup: 'people-group',
114623  *                 ptype: 'gridviewdragdrop',
114624  *                 enableDrop: false
114625  *             }
114626  *         }
114627  *     });
114628  */
114629 Ext.define('Ext.grid.View', {
114630     extend: 'Ext.view.Table',
114631     alias: 'widget.gridview',
114632
114633     /**
114634      * @cfg {Boolean} stripeRows <tt>true</tt> to stripe the rows. Default is <tt>true</tt>.
114635      * <p>This causes the CSS class <tt><b>x-grid-row-alt</b></tt> to be added to alternate rows of
114636      * the grid. A default CSS rule is provided which sets a background color, but you can override this
114637      * with a rule which either overrides the <b>background-color</b> style using the '!important'
114638      * modifier, or which uses a CSS selector of higher specificity.</p>
114639      */
114640     stripeRows: true,
114641
114642     invalidateScrollerOnRefresh: true,
114643
114644     /**
114645      * Scroll the GridView to the top by scrolling the scroller.
114646      * @private
114647      */
114648     scrollToTop : function(){
114649         if (this.rendered) {
114650             var section = this.ownerCt,
114651                 verticalScroller = section.verticalScroller;
114652
114653             if (verticalScroller) {
114654                 verticalScroller.scrollToTop();
114655             }
114656         }
114657     },
114658
114659     // after adding a row stripe rows from then on
114660     onAdd: function(ds, records, index) {
114661         this.callParent(arguments);
114662         this.doStripeRows(index);
114663     },
114664
114665     // after removing a row stripe rows from then on
114666     onRemove: function(ds, records, index) {
114667         this.callParent(arguments);
114668         this.doStripeRows(index);
114669     },
114670
114671     onUpdate: function(ds, record, operation) {
114672         var index = ds.indexOf(record);
114673         this.callParent(arguments);
114674         this.doStripeRows(index, index);
114675     },
114676
114677     /**
114678      * Stripe rows from a particular row index
114679      * @param {Number} startRow
114680      * @param {Number} endRow (Optional) argument specifying the last row to process. By default process up to the last row.
114681      * @private
114682      */
114683     doStripeRows: function(startRow, endRow) {
114684         // ensure stripeRows configuration is turned on
114685         if (this.stripeRows) {
114686             var rows   = this.getNodes(startRow, endRow),
114687                 rowsLn = rows.length,
114688                 i      = 0,
114689                 row;
114690
114691             for (; i < rowsLn; i++) {
114692                 row = rows[i];
114693                 // Remove prior applied row classes.
114694                 row.className = row.className.replace(this.rowClsRe, ' ');
114695                 startRow++;
114696                 // Every odd row will get an additional cls
114697                 if (startRow % 2 === 0) {
114698                     row.className += (' ' + this.altRowCls);
114699                 }
114700             }
114701         }
114702     },
114703
114704     refresh: function(firstPass) {
114705         this.callParent(arguments);
114706         this.doStripeRows(0);
114707         // TODO: Remove gridpanel dependency
114708         var g = this.up('gridpanel');
114709         if (g && this.invalidateScrollerOnRefresh) {
114710             g.invalidateScroller();
114711         }
114712     }
114713 });
114714
114715 /**
114716  * @author Aaron Conran
114717  * @docauthor Ed Spencer
114718  *
114719  * Grids are an excellent way of showing large amounts of tabular data on the client side. Essentially a supercharged
114720  * `<table>`, GridPanel makes it easy to fetch, sort and filter large amounts of data.
114721  *
114722  * Grids are composed of two main pieces - a {@link Ext.data.Store Store} full of data and a set of columns to render.
114723  *
114724  * ## Basic GridPanel
114725  *
114726  *     @example
114727  *     Ext.create('Ext.data.Store', {
114728  *         storeId:'simpsonsStore',
114729  *         fields:['name', 'email', 'phone'],
114730  *         data:{'items':[
114731  *             { 'name': 'Lisa',  "email":"lisa@simpsons.com",  "phone":"555-111-1224"  },
114732  *             { 'name': 'Bart',  "email":"bart@simpsons.com",  "phone":"555-222-1234" },
114733  *             { 'name': 'Homer', "email":"home@simpsons.com",  "phone":"555-222-1244"  },
114734  *             { 'name': 'Marge', "email":"marge@simpsons.com", "phone":"555-222-1254"  }
114735  *         ]},
114736  *         proxy: {
114737  *             type: 'memory',
114738  *             reader: {
114739  *                 type: 'json',
114740  *                 root: 'items'
114741  *             }
114742  *         }
114743  *     });
114744  *
114745  *     Ext.create('Ext.grid.Panel', {
114746  *         title: 'Simpsons',
114747  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
114748  *         columns: [
114749  *             { header: 'Name',  dataIndex: 'name' },
114750  *             { header: 'Email', dataIndex: 'email', flex: 1 },
114751  *             { header: 'Phone', dataIndex: 'phone' }
114752  *         ],
114753  *         height: 200,
114754  *         width: 400,
114755  *         renderTo: Ext.getBody()
114756  *     });
114757  *
114758  * The code above produces a simple grid with three columns. We specified a Store which will load JSON data inline.
114759  * In most apps we would be placing the grid inside another container and wouldn't need to use the
114760  * {@link #height}, {@link #width} and {@link #renderTo} configurations but they are included here to make it easy to get
114761  * up and running.
114762  *
114763  * The grid we created above will contain a header bar with a title ('Simpsons'), a row of column headers directly underneath
114764  * and finally the grid rows under the headers.
114765  *
114766  * ## Configuring columns
114767  *
114768  * By default, each column is sortable and will toggle between ASC and DESC sorting when you click on its header. Each
114769  * column header is also reorderable by default, and each gains a drop-down menu with options to hide and show columns.
114770  * It's easy to configure each column - here we use the same example as above and just modify the columns config:
114771  *
114772  *     columns: [
114773  *         {
114774  *             header: 'Name',
114775  *             dataIndex: 'name',
114776  *             sortable: false,
114777  *             hideable: false,
114778  *             flex: 1
114779  *         },
114780  *         {
114781  *             header: 'Email',
114782  *             dataIndex: 'email',
114783  *             hidden: true
114784  *         },
114785  *         {
114786  *             header: 'Phone',
114787  *             dataIndex: 'phone',
114788  *             width: 100
114789  *         }
114790  *     ]
114791  *
114792  * We turned off sorting and hiding on the 'Name' column so clicking its header now has no effect. We also made the Email
114793  * column hidden by default (it can be shown again by using the menu on any other column). We also set the Phone column to
114794  * a fixed with of 100px and flexed the Name column, which means it takes up all remaining width after the other columns
114795  * have been accounted for. See the {@link Ext.grid.column.Column column docs} for more details.
114796  *
114797  * ## Renderers
114798  *
114799  * As well as customizing columns, it's easy to alter the rendering of individual cells using renderers. A renderer is
114800  * tied to a particular column and is passed the value that would be rendered into each cell in that column. For example,
114801  * we could define a renderer function for the email column to turn each email address into a mailto link:
114802  *
114803  *     columns: [
114804  *         {
114805  *             header: 'Email',
114806  *             dataIndex: 'email',
114807  *             renderer: function(value) {
114808  *                 return Ext.String.format('<a href="mailto:{0}">{1}</a>', value, value);
114809  *             }
114810  *         }
114811  *     ]
114812  *
114813  * See the {@link Ext.grid.column.Column column docs} for more information on renderers.
114814  *
114815  * ## Selection Models
114816  *
114817  * Sometimes all you want is to render data onto the screen for viewing, but usually it's necessary to interact with or
114818  * update that data. Grids use a concept called a Selection Model, which is simply a mechanism for selecting some part of
114819  * the data in the grid. The two main types of Selection Model are RowSelectionModel, where entire rows are selected, and
114820  * CellSelectionModel, where individual cells are selected.
114821  *
114822  * Grids use a Row Selection Model by default, but this is easy to customise like so:
114823  *
114824  *     Ext.create('Ext.grid.Panel', {
114825  *         selType: 'cellmodel',
114826  *         store: ...
114827  *     });
114828  *
114829  * Specifying the `cellmodel` changes a couple of things. Firstly, clicking on a cell now
114830  * selects just that cell (using a {@link Ext.selection.RowModel rowmodel} will select the entire row), and secondly the
114831  * keyboard navigation will walk from cell to cell instead of row to row. Cell-based selection models are usually used in
114832  * conjunction with editing.
114833  *
114834  * ## Editing
114835  *
114836  * Grid has built-in support for in-line editing. There are two chief editing modes - cell editing and row editing. Cell
114837  * editing is easy to add to your existing column setup - here we'll just modify the example above to include an editor
114838  * on both the name and the email columns:
114839  *
114840  *     Ext.create('Ext.grid.Panel', {
114841  *         title: 'Simpsons',
114842  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
114843  *         columns: [
114844  *             { header: 'Name',  dataIndex: 'name', field: 'textfield' },
114845  *             { header: 'Email', dataIndex: 'email', flex: 1,
114846  *                 field: {
114847  *                     xtype: 'textfield',
114848  *                     allowBlank: false
114849  *                 }
114850  *             },
114851  *             { header: 'Phone', dataIndex: 'phone' }
114852  *         ],
114853  *         selType: 'cellmodel',
114854  *         plugins: [
114855  *             Ext.create('Ext.grid.plugin.CellEditing', {
114856  *                 clicksToEdit: 1
114857  *             })
114858  *         ],
114859  *         height: 200,
114860  *         width: 400,
114861  *         renderTo: Ext.getBody()
114862  *     });
114863  *
114864  * This requires a little explanation. We're passing in {@link #store store} and {@link #columns columns} as normal, but
114865  * this time we've also specified a {@link Ext.grid.column.Column#field field} on two of our columns. For the Name column
114866  * we just want a default textfield to edit the value, so we specify 'textfield'. For the Email column we customized the
114867  * editor slightly by passing allowBlank: false, which will provide inline validation.
114868  *
114869  * To support cell editing, we also specified that the grid should use the 'cellmodel' {@link #selType}, and created an
114870  * instance of the {@link Ext.grid.plugin.CellEditing CellEditing plugin}, which we configured to activate each editor after a
114871  * single click.
114872  *
114873  * ## Row Editing
114874  *
114875  * The other type of editing is row-based editing, using the RowEditor component. This enables you to edit an entire row
114876  * at a time, rather than editing cell by cell. Row Editing works in exactly the same way as cell editing, all we need to
114877  * do is change the plugin type to {@link Ext.grid.plugin.RowEditing}, and set the selType to 'rowmodel':
114878  *
114879  *     Ext.create('Ext.grid.Panel', {
114880  *         title: 'Simpsons',
114881  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
114882  *         columns: [
114883  *             { header: 'Name',  dataIndex: 'name', field: 'textfield' },
114884  *             { header: 'Email', dataIndex: 'email', flex:1,
114885  *                 field: {
114886  *                     xtype: 'textfield',
114887  *                     allowBlank: false
114888  *                 }
114889  *             },
114890  *             { header: 'Phone', dataIndex: 'phone' }
114891  *         ],
114892  *         selType: 'rowmodel',
114893  *         plugins: [
114894  *             Ext.create('Ext.grid.plugin.RowEditing', {
114895  *                 clicksToEdit: 1
114896  *             })
114897  *         ],
114898  *         height: 200,
114899  *         width: 400,
114900  *         renderTo: Ext.getBody()
114901  *     });
114902  *
114903  * Again we passed some configuration to our {@link Ext.grid.plugin.RowEditing} plugin, and now when we click each row a row
114904  * editor will appear and enable us to edit each of the columns we have specified an editor for.
114905  *
114906  * ## Sorting & Filtering
114907  *
114908  * Every grid is attached to a {@link Ext.data.Store Store}, which provides multi-sort and filtering capabilities. It's
114909  * easy to set up a grid to be sorted from the start:
114910  *
114911  *     var myGrid = Ext.create('Ext.grid.Panel', {
114912  *         store: {
114913  *             fields: ['name', 'email', 'phone'],
114914  *             sorters: ['name', 'phone']
114915  *         },
114916  *         columns: [
114917  *             { text: 'Name',  dataIndex: 'name' },
114918  *             { text: 'Email', dataIndex: 'email' }
114919  *         ]
114920  *     });
114921  *
114922  * Sorting at run time is easily accomplished by simply clicking each column header. If you need to perform sorting on
114923  * more than one field at run time it's easy to do so by adding new sorters to the store:
114924  *
114925  *     myGrid.store.sort([
114926  *         { property: 'name',  direction: 'ASC' },
114927  *         { property: 'email', direction: 'DESC' }
114928  *     ]);
114929  *
114930  * See {@link Ext.data.Store} for examples of filtering.
114931  *
114932  * ## Grouping
114933  *
114934  * Grid supports the grouping of rows by any field. For example if we had a set of employee records, we might want to
114935  * group by the department that each employee works in. Here's how we might set that up:
114936  *
114937  *     @example
114938  *     var store = Ext.create('Ext.data.Store', {
114939  *         storeId:'employeeStore',
114940  *         fields:['name', 'senority', 'department'],
114941  *         groupField: 'department',
114942  *         data: {'employees':[
114943  *             { "name": "Michael Scott",  "senority": 7, "department": "Manangement" },
114944  *             { "name": "Dwight Schrute", "senority": 2, "department": "Sales" },
114945  *             { "name": "Jim Halpert",    "senority": 3, "department": "Sales" },
114946  *             { "name": "Kevin Malone",   "senority": 4, "department": "Accounting" },
114947  *             { "name": "Angela Martin",  "senority": 5, "department": "Accounting" }
114948  *         ]},
114949  *         proxy: {
114950  *             type: 'memory',
114951  *             reader: {
114952  *                 type: 'json',
114953  *                 root: 'employees'
114954  *             }
114955  *         }
114956  *     });
114957  *
114958  *     Ext.create('Ext.grid.Panel', {
114959  *         title: 'Employees',
114960  *         store: Ext.data.StoreManager.lookup('employeeStore'),
114961  *         columns: [
114962  *             { header: 'Name',     dataIndex: 'name' },
114963  *             { header: 'Senority', dataIndex: 'senority' }
114964  *         ],
114965  *         features: [{ftype:'grouping'}],
114966  *         width: 200,
114967  *         height: 275,
114968  *         renderTo: Ext.getBody()
114969  *     });
114970  *
114971  * ## Infinite Scrolling
114972  *
114973  * Grid supports infinite scrolling as an alternative to using a paging toolbar. Your users can scroll through thousands
114974  * of records without the performance penalties of renderering all the records on screen at once. The grid should be bound
114975  * to a store with a pageSize specified.
114976  *
114977  *     var grid = Ext.create('Ext.grid.Panel', {
114978  *         // Use a PagingGridScroller (this is interchangeable with a PagingToolbar)
114979  *         verticalScrollerType: 'paginggridscroller',
114980  *         // do not reset the scrollbar when the view refreshs
114981  *         invalidateScrollerOnRefresh: false,
114982  *         // infinite scrolling does not support selection
114983  *         disableSelection: true,
114984  *         // ...
114985  *     });
114986  *
114987  * ## Paging
114988  *
114989  * Grid supports paging through large sets of data via a PagingToolbar or PagingGridScroller (see the Infinite Scrolling section above).
114990  * To leverage paging via a toolbar or scroller, you need to set a pageSize configuration on the Store.
114991  *
114992  *     @example
114993  *     var itemsPerPage = 2;   // set the number of items you want per page
114994  *
114995  *     var store = Ext.create('Ext.data.Store', {
114996  *         id:'simpsonsStore',
114997  *         autoLoad: false,
114998  *         fields:['name', 'email', 'phone'],
114999  *         pageSize: itemsPerPage, // items per page
115000  *         proxy: {
115001  *             type: 'ajax',
115002  *             url: 'pagingstore.js',  // url that will load data with respect to start and limit params
115003  *             reader: {
115004  *                 type: 'json',
115005  *                 root: 'items',
115006  *                 totalProperty: 'total'
115007  *             }
115008  *         }
115009  *     });
115010  *
115011  *     // specify segment of data you want to load using params
115012  *     store.load({
115013  *         params:{
115014  *             start:0,
115015  *             limit: itemsPerPage
115016  *         }
115017  *     });
115018  *
115019  *     Ext.create('Ext.grid.Panel', {
115020  *         title: 'Simpsons',
115021  *         store: store,
115022  *         columns: [
115023  *             {header: 'Name',  dataIndex: 'name'},
115024  *             {header: 'Email', dataIndex: 'email', flex:1},
115025  *             {header: 'Phone', dataIndex: 'phone'}
115026  *         ],
115027  *         width: 400,
115028  *         height: 125,
115029  *         dockedItems: [{
115030  *             xtype: 'pagingtoolbar',
115031  *             store: store,   // same store GridPanel is using
115032  *             dock: 'bottom',
115033  *             displayInfo: true
115034  *         }],
115035  *         renderTo: Ext.getBody()
115036  *     });
115037  */
115038 Ext.define('Ext.grid.Panel', {
115039     extend: 'Ext.panel.Table',
115040     requires: ['Ext.grid.View'],
115041     alias: ['widget.gridpanel', 'widget.grid'],
115042     alternateClassName: ['Ext.list.ListView', 'Ext.ListView', 'Ext.grid.GridPanel'],
115043     viewType: 'gridview',
115044
115045     lockable: false,
115046
115047     // Required for the Lockable Mixin. These are the configurations which will be copied to the
115048     // normal and locked sub tablepanels
115049     normalCfgCopy: ['invalidateScrollerOnRefresh', 'verticalScroller', 'verticalScrollDock', 'verticalScrollerType', 'scroll'],
115050     lockedCfgCopy: ['invalidateScrollerOnRefresh'],
115051
115052     /**
115053      * @cfg {Boolean} [columnLines=false] Adds column line styling
115054      */
115055
115056     initComponent: function() {
115057         var me = this;
115058
115059         if (me.columnLines) {
115060             me.setColumnLines(me.columnLines);
115061         }
115062
115063         me.callParent();
115064     },
115065
115066     setColumnLines: function(show) {
115067         var me = this,
115068             method = (show) ? 'addClsWithUI' : 'removeClsWithUI';
115069
115070         me[method]('with-col-lines');
115071     }
115072 });
115073
115074 // Currently has the following issues:
115075 // - Does not handle postEditValue
115076 // - Fields without editors need to sync with their values in Store
115077 // - starting to edit another record while already editing and dirty should probably prevent it
115078 // - aggregating validation messages
115079 // - tabIndex is not managed bc we leave elements in dom, and simply move via positioning
115080 // - layout issues when changing sizes/width while hidden (layout bug)
115081
115082 /**
115083  * @class Ext.grid.RowEditor
115084  * @extends Ext.form.Panel
115085  *
115086  * Internal utility class used to provide row editing functionality. For developers, they should use
115087  * the RowEditing plugin to use this functionality with a grid.
115088  *
115089  * @ignore
115090  */
115091 Ext.define('Ext.grid.RowEditor', {
115092     extend: 'Ext.form.Panel',
115093     requires: [
115094         'Ext.tip.ToolTip',
115095         'Ext.util.HashMap',
115096         'Ext.util.KeyNav'
115097     ],
115098
115099     saveBtnText  : 'Update',
115100     cancelBtnText: 'Cancel',
115101     errorsText: 'Errors',
115102     dirtyText: 'You need to commit or cancel your changes',
115103
115104     lastScrollLeft: 0,
115105     lastScrollTop: 0,
115106
115107     border: false,
115108     
115109     // Change the hideMode to offsets so that we get accurate measurements when
115110     // the roweditor is hidden for laying out things like a TriggerField.
115111     hideMode: 'offsets',
115112
115113     initComponent: function() {
115114         var me = this,
115115             form;
115116
115117         me.cls = Ext.baseCSSPrefix + 'grid-row-editor';
115118
115119         me.layout = {
115120             type: 'hbox',
115121             align: 'middle'
115122         };
115123
115124         // Maintain field-to-column mapping
115125         // It's easy to get a field from a column, but not vice versa
115126         me.columns = Ext.create('Ext.util.HashMap');
115127         me.columns.getKey = function(columnHeader) {
115128             var f;
115129             if (columnHeader.getEditor) {
115130                 f = columnHeader.getEditor();
115131                 if (f) {
115132                     return f.id;
115133                 }
115134             }
115135             return columnHeader.id;
115136         };
115137         me.mon(me.columns, {
115138             add: me.onFieldAdd,
115139             remove: me.onFieldRemove,
115140             replace: me.onFieldReplace,
115141             scope: me
115142         });
115143
115144         me.callParent(arguments);
115145
115146         if (me.fields) {
115147             me.setField(me.fields);
115148             delete me.fields;
115149         }
115150
115151         form = me.getForm();
115152         form.trackResetOnLoad = true;
115153     },
115154
115155     onFieldChange: function() {
115156         var me = this,
115157             form = me.getForm(),
115158             valid = form.isValid();
115159         if (me.errorSummary && me.isVisible()) {
115160             me[valid ? 'hideToolTip' : 'showToolTip']();
115161         }
115162         if (me.floatingButtons) {
115163             me.floatingButtons.child('#update').setDisabled(!valid);
115164         }
115165         me.isValid = valid;
115166     },
115167
115168     afterRender: function() {
115169         var me = this,
115170             plugin = me.editingPlugin;
115171
115172         me.callParent(arguments);
115173         me.mon(me.renderTo, 'scroll', me.onCtScroll, me, { buffer: 100 });
115174
115175         // Prevent from bubbling click events to the grid view
115176         me.mon(me.el, {
115177             click: Ext.emptyFn,
115178             stopPropagation: true
115179         });
115180
115181         me.el.swallowEvent([
115182             'keypress',
115183             'keydown'
115184         ]);
115185
115186         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
115187             enter: plugin.completeEdit,
115188             esc: plugin.onEscKey,
115189             scope: plugin
115190         });
115191
115192         me.mon(plugin.view, {
115193             beforerefresh: me.onBeforeViewRefresh,
115194             refresh: me.onViewRefresh,
115195             scope: me
115196         });
115197     },
115198
115199     onBeforeViewRefresh: function(view) {
115200         var me = this,
115201             viewDom = view.el.dom;
115202
115203         if (me.el.dom.parentNode === viewDom) {
115204             viewDom.removeChild(me.el.dom);
115205         }
115206     },
115207
115208     onViewRefresh: function(view) {
115209         var me = this,
115210             viewDom = view.el.dom,
115211             context = me.context,
115212             idx;
115213
115214         viewDom.appendChild(me.el.dom);
115215
115216         // Recover our row node after a view refresh
115217         if (context && (idx = context.store.indexOf(context.record)) >= 0) {
115218             context.row = view.getNode(idx);
115219             me.reposition();
115220             if (me.tooltip && me.tooltip.isVisible()) {
115221                 me.tooltip.setTarget(context.row);
115222             }
115223         } else {
115224             me.editingPlugin.cancelEdit();
115225         }
115226     },
115227
115228     onCtScroll: function(e, target) {
115229         var me = this,
115230             scrollTop  = target.scrollTop,
115231             scrollLeft = target.scrollLeft;
115232
115233         if (scrollTop !== me.lastScrollTop) {
115234             me.lastScrollTop = scrollTop;
115235             if ((me.tooltip && me.tooltip.isVisible()) || me.hiddenTip) {
115236                 me.repositionTip();
115237             }
115238         }
115239         if (scrollLeft !== me.lastScrollLeft) {
115240             me.lastScrollLeft = scrollLeft;
115241             me.reposition();
115242         }
115243     },
115244
115245     onColumnAdd: function(column) {
115246         this.setField(column);
115247     },
115248
115249     onColumnRemove: function(column) {
115250         this.columns.remove(column);
115251     },
115252
115253     onColumnResize: function(column, width) {
115254         column.getEditor().setWidth(width - 2);
115255         if (this.isVisible()) {
115256             this.reposition();
115257         }
115258     },
115259
115260     onColumnHide: function(column) {
115261         column.getEditor().hide();
115262         if (this.isVisible()) {
115263             this.reposition();
115264         }
115265     },
115266
115267     onColumnShow: function(column) {
115268         var field = column.getEditor();
115269         field.setWidth(column.getWidth() - 2).show();
115270         if (this.isVisible()) {
115271             this.reposition();
115272         }
115273     },
115274
115275     onColumnMove: function(column, fromIdx, toIdx) {
115276         var field = column.getEditor();
115277         if (this.items.indexOf(field) != toIdx) {
115278             this.move(fromIdx, toIdx);
115279         }
115280     },
115281
115282     onFieldAdd: function(map, fieldId, column) {
115283         var me = this,
115284             colIdx = me.editingPlugin.grid.headerCt.getHeaderIndex(column),
115285             field = column.getEditor({ xtype: 'displayfield' });
115286
115287         me.insert(colIdx, field);
115288     },
115289
115290     onFieldRemove: function(map, fieldId, column) {
115291         var me = this,
115292             field = column.getEditor(),
115293             fieldEl = field.el;
115294         me.remove(field, false);
115295         if (fieldEl) {
115296             fieldEl.remove();
115297         }
115298     },
115299
115300     onFieldReplace: function(map, fieldId, column, oldColumn) {
115301         var me = this;
115302         me.onFieldRemove(map, fieldId, oldColumn);
115303     },
115304
115305     clearFields: function() {
115306         var me = this,
115307             map = me.columns;
115308         map.each(function(fieldId) {
115309             map.removeAtKey(fieldId);
115310         });
115311     },
115312
115313     getFloatingButtons: function() {
115314         var me = this,
115315             cssPrefix = Ext.baseCSSPrefix,
115316             btnsCss = cssPrefix + 'grid-row-editor-buttons',
115317             plugin = me.editingPlugin,
115318             btns;
115319
115320         if (!me.floatingButtons) {
115321             btns = me.floatingButtons = Ext.create('Ext.Container', {
115322                 renderTpl: [
115323                     '<div class="{baseCls}-ml"></div>',
115324                     '<div class="{baseCls}-mr"></div>',
115325                     '<div class="{baseCls}-bl"></div>',
115326                     '<div class="{baseCls}-br"></div>',
115327                     '<div class="{baseCls}-bc"></div>'
115328                 ],
115329
115330                 renderTo: me.el,
115331                 baseCls: btnsCss,
115332                 layout: {
115333                     type: 'hbox',
115334                     align: 'middle'
115335                 },
115336                 defaults: {
115337                     margins: '0 1 0 1'
115338                 },
115339                 items: [{
115340                     itemId: 'update',
115341                     flex: 1,
115342                     xtype: 'button',
115343                     handler: plugin.completeEdit,
115344                     scope: plugin,
115345                     text: me.saveBtnText,
115346                     disabled: !me.isValid
115347                 }, {
115348                     flex: 1,
115349                     xtype: 'button',
115350                     handler: plugin.cancelEdit,
115351                     scope: plugin,
115352                     text: me.cancelBtnText
115353                 }]
115354             });
115355
115356             // Prevent from bubbling click events to the grid view
115357             me.mon(btns.el, {
115358                 // BrowserBug: Opera 11.01
115359                 //   causes the view to scroll when a button is focused from mousedown
115360                 mousedown: Ext.emptyFn,
115361                 click: Ext.emptyFn,
115362                 stopEvent: true
115363             });
115364         }
115365         return me.floatingButtons;
115366     },
115367
115368     reposition: function(animateConfig) {
115369         var me = this,
115370             context = me.context,
115371             row = context && Ext.get(context.row),
115372             btns = me.getFloatingButtons(),
115373             btnEl = btns.el,
115374             grid = me.editingPlugin.grid,
115375             viewEl = grid.view.el,
115376             scroller = grid.verticalScroller,
115377
115378             // always get data from ColumnModel as its what drives
115379             // the GridView's sizing
115380             mainBodyWidth = grid.headerCt.getFullWidth(),
115381             scrollerWidth = grid.getWidth(),
115382
115383             // use the minimum as the columns may not fill up the entire grid
115384             // width
115385             width = Math.min(mainBodyWidth, scrollerWidth),
115386             scrollLeft = grid.view.el.dom.scrollLeft,
115387             btnWidth = btns.getWidth(),
115388             left = (width - btnWidth) / 2 + scrollLeft,
115389             y, rowH, newHeight,
115390
115391             invalidateScroller = function() {
115392                 if (scroller) {
115393                     scroller.invalidate();
115394                     btnEl.scrollIntoView(viewEl, false);
115395                 }
115396                 if (animateConfig && animateConfig.callback) {
115397                     animateConfig.callback.call(animateConfig.scope || me);
115398                 }
115399             };
115400
115401         // need to set both top/left
115402         if (row && Ext.isElement(row.dom)) {
115403             // Bring our row into view if necessary, so a row editor that's already
115404             // visible and animated to the row will appear smooth
115405             row.scrollIntoView(viewEl, false);
115406
115407             // Get the y position of the row relative to its top-most static parent.
115408             // offsetTop will be relative to the table, and is incorrect
115409             // when mixed with certain grid features (e.g., grouping).
115410             y = row.getXY()[1] - 5;
115411             rowH = row.getHeight();
115412             newHeight = rowH + 10;
115413
115414             // IE doesn't set the height quite right.
115415             // This isn't a border-box issue, it even happens
115416             // in IE8 and IE7 quirks.
115417             // TODO: Test in IE9!
115418             if (Ext.isIE) {
115419                 newHeight += 2;
115420             }
115421
115422             // Set editor height to match the row height
115423             if (me.getHeight() != newHeight) {
115424                 me.setHeight(newHeight);
115425                 me.el.setLeft(0);
115426             }
115427
115428             if (animateConfig) {
115429                 var animObj = {
115430                     to: {
115431                         y: y
115432                     },
115433                     duration: animateConfig.duration || 125,
115434                     listeners: {
115435                         afteranimate: function() {
115436                             invalidateScroller();
115437                             y = row.getXY()[1] - 5;
115438                             me.el.setY(y);
115439                         }
115440                     }
115441                 };
115442                 me.animate(animObj);
115443             } else {
115444                 me.el.setY(y);
115445                 invalidateScroller();
115446             }
115447         }
115448         if (me.getWidth() != mainBodyWidth) {
115449             me.setWidth(mainBodyWidth);
115450         }
115451         btnEl.setLeft(left);
115452     },
115453
115454     getEditor: function(fieldInfo) {
115455         var me = this;
115456
115457         if (Ext.isNumber(fieldInfo)) {
115458             // Query only form fields. This just future-proofs us in case we add
115459             // other components to RowEditor later on.  Don't want to mess with
115460             // indices.
115461             return me.query('>[isFormField]')[fieldInfo];
115462         } else if (fieldInfo instanceof Ext.grid.column.Column) {
115463             return fieldInfo.getEditor();
115464         }
115465     },
115466
115467     removeField: function(field) {
115468         var me = this;
115469
115470         // Incase we pass a column instead, which is fine
115471         field = me.getEditor(field);
115472         me.mun(field, 'validitychange', me.onValidityChange, me);
115473
115474         // Remove field/column from our mapping, which will fire the event to
115475         // remove the field from our container
115476         me.columns.removeKey(field.id);
115477     },
115478
115479     setField: function(column) {
115480         var me = this,
115481             field;
115482
115483         if (Ext.isArray(column)) {
115484             Ext.Array.forEach(column, me.setField, me);
115485             return;
115486         }
115487
115488         // Get a default display field if necessary
115489         field = column.getEditor(null, {
115490             xtype: 'displayfield',
115491             // Default display fields will not return values. This is done because
115492             // the display field will pick up column renderers from the grid.
115493             getModelData: function() {
115494                 return null;
115495             }
115496         });
115497         field.margins = '0 0 0 2';
115498         field.setWidth(column.getDesiredWidth() - 2);
115499         me.mon(field, 'change', me.onFieldChange, me);
115500
115501         // Maintain mapping of fields-to-columns
115502         // This will fire events that maintain our container items
115503         me.columns.add(field.id, column);
115504         if (column.hidden) {
115505             me.onColumnHide(column);
115506         }
115507         if (me.isVisible() && me.context) {
115508             me.renderColumnData(field, me.context.record);
115509         }
115510     },
115511
115512     loadRecord: function(record) {
115513         var me = this,
115514             form = me.getForm();
115515         form.loadRecord(record);
115516         if (form.isValid()) {
115517             me.hideToolTip();
115518         } else {
115519             me.showToolTip();
115520         }
115521
115522         // render display fields so they honor the column renderer/template
115523         Ext.Array.forEach(me.query('>displayfield'), function(field) {
115524             me.renderColumnData(field, record);
115525         }, me);
115526     },
115527
115528     renderColumnData: function(field, record) {
115529         var me = this,
115530             grid = me.editingPlugin.grid,
115531             headerCt = grid.headerCt,
115532             view = grid.view,
115533             store = view.store,
115534             column = me.columns.get(field.id),
115535             value = record.get(column.dataIndex);
115536
115537         // honor our column's renderer (TemplateHeader sets renderer for us!)
115538         if (column.renderer) {
115539             var metaData = { tdCls: '', style: '' },
115540                 rowIdx = store.indexOf(record),
115541                 colIdx = headerCt.getHeaderIndex(column);
115542
115543             value = column.renderer.call(
115544                 column.scope || headerCt.ownerCt,
115545                 value,
115546                 metaData,
115547                 record,
115548                 rowIdx,
115549                 colIdx,
115550                 store,
115551                 view
115552             );
115553         }
115554
115555         field.setRawValue(value);
115556         field.resetOriginalValue();
115557     },
115558
115559     beforeEdit: function() {
115560         var me = this;
115561
115562         if (me.isVisible() && !me.autoCancel && me.isDirty()) {
115563             me.showToolTip();
115564             return false;
115565         }
115566     },
115567
115568     /**
115569      * Start editing the specified grid at the specified position.
115570      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
115571      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited.
115572      */
115573     startEdit: function(record, columnHeader) {
115574         var me = this,
115575             grid = me.editingPlugin.grid,
115576             view = grid.getView(),
115577             store = grid.store,
115578             context = me.context = Ext.apply(me.editingPlugin.context, {
115579                 view: grid.getView(),
115580                 store: store
115581             });
115582
115583         // make sure our row is selected before editing
115584         context.grid.getSelectionModel().select(record);
115585
115586         // Reload the record data
115587         me.loadRecord(record);
115588
115589         if (!me.isVisible()) {
115590             me.show();
115591             me.focusContextCell();
115592         } else {
115593             me.reposition({
115594                 callback: this.focusContextCell
115595             });
115596         }
115597     },
115598
115599     // Focus the cell on start edit based upon the current context
115600     focusContextCell: function() {
115601         var field = this.getEditor(this.context.colIdx);
115602         if (field && field.focus) {
115603             field.focus();
115604         }
115605     },
115606
115607     cancelEdit: function() {
115608         var me = this,
115609             form = me.getForm();
115610
115611         me.hide();
115612         form.clearInvalid();
115613         form.reset();
115614     },
115615
115616     completeEdit: function() {
115617         var me = this,
115618             form = me.getForm();
115619
115620         if (!form.isValid()) {
115621             return;
115622         }
115623
115624         form.updateRecord(me.context.record);
115625         me.hide();
115626         return true;
115627     },
115628
115629     onShow: function() {
115630         var me = this;
115631         me.callParent(arguments);
115632         me.reposition();
115633     },
115634
115635     onHide: function() {
115636         var me = this;
115637         me.callParent(arguments);
115638         me.hideToolTip();
115639         me.invalidateScroller();
115640         if (me.context) {
115641             me.context.view.focus();
115642             me.context = null;
115643         }
115644     },
115645
115646     isDirty: function() {
115647         var me = this,
115648             form = me.getForm();
115649         return form.isDirty();
115650     },
115651
115652     getToolTip: function() {
115653         var me = this,
115654             tip;
115655
115656         if (!me.tooltip) {
115657             tip = me.tooltip = Ext.createWidget('tooltip', {
115658                 cls: Ext.baseCSSPrefix + 'grid-row-editor-errors',
115659                 title: me.errorsText,
115660                 autoHide: false,
115661                 closable: true,
115662                 closeAction: 'disable',
115663                 anchor: 'left'
115664             });
115665         }
115666         return me.tooltip;
115667     },
115668
115669     hideToolTip: function() {
115670         var me = this,
115671             tip = me.getToolTip();
115672         if (tip.rendered) {
115673             tip.disable();
115674         }
115675         me.hiddenTip = false;
115676     },
115677
115678     showToolTip: function() {
115679         var me = this,
115680             tip = me.getToolTip(),
115681             context = me.context,
115682             row = Ext.get(context.row),
115683             viewEl = context.grid.view.el;
115684
115685         tip.setTarget(row);
115686         tip.showAt([-10000, -10000]);
115687         tip.body.update(me.getErrors());
115688         tip.mouseOffset = [viewEl.getWidth() - row.getWidth() + me.lastScrollLeft + 15, 0];
115689         me.repositionTip();
115690         tip.doLayout();
115691         tip.enable();
115692     },
115693
115694     repositionTip: function() {
115695         var me = this,
115696             tip = me.getToolTip(),
115697             context = me.context,
115698             row = Ext.get(context.row),
115699             viewEl = context.grid.view.el,
115700             viewHeight = viewEl.getHeight(),
115701             viewTop = me.lastScrollTop,
115702             viewBottom = viewTop + viewHeight,
115703             rowHeight = row.getHeight(),
115704             rowTop = row.dom.offsetTop,
115705             rowBottom = rowTop + rowHeight;
115706
115707         if (rowBottom > viewTop && rowTop < viewBottom) {
115708             tip.show();
115709             me.hiddenTip = false;
115710         } else {
115711             tip.hide();
115712             me.hiddenTip = true;
115713         }
115714     },
115715
115716     getErrors: function() {
115717         var me = this,
115718             dirtyText = !me.autoCancel && me.isDirty() ? me.dirtyText + '<br />' : '',
115719             errors = [];
115720
115721         Ext.Array.forEach(me.query('>[isFormField]'), function(field) {
115722             errors = errors.concat(
115723                 Ext.Array.map(field.getErrors(), function(e) {
115724                     return '<li>' + e + '</li>';
115725                 })
115726             );
115727         }, me);
115728
115729         return dirtyText + '<ul>' + errors.join('') + '</ul>';
115730     },
115731
115732     invalidateScroller: function() {
115733         var me = this,
115734             context = me.context,
115735             scroller = context.grid.verticalScroller;
115736
115737         if (scroller) {
115738             scroller.invalidate();
115739         }
115740     }
115741 });
115742 /**
115743  * @class Ext.grid.header.Container
115744  * @extends Ext.container.Container
115745  *
115746  * Container which holds headers and is docked at the top or bottom of a TablePanel.
115747  * The HeaderContainer drives resizing/moving/hiding of columns within the TableView.
115748  * As headers are hidden, moved or resized the headercontainer is responsible for
115749  * triggering changes within the view.
115750  */
115751 Ext.define('Ext.grid.header.Container', {
115752     extend: 'Ext.container.Container',
115753     uses: [
115754         'Ext.grid.ColumnLayout',
115755         'Ext.grid.column.Column',
115756         'Ext.menu.Menu',
115757         'Ext.menu.CheckItem',
115758         'Ext.menu.Separator',
115759         'Ext.grid.plugin.HeaderResizer',
115760         'Ext.grid.plugin.HeaderReorderer'
115761     ],
115762     border: true,
115763
115764     alias: 'widget.headercontainer',
115765
115766     baseCls: Ext.baseCSSPrefix + 'grid-header-ct',
115767     dock: 'top',
115768
115769     /**
115770      * @cfg {Number} weight
115771      * HeaderContainer overrides the default weight of 0 for all docked items to 100.
115772      * This is so that it has more priority over things like toolbars.
115773      */
115774     weight: 100,
115775     defaultType: 'gridcolumn',
115776     /**
115777      * @cfg {Number} defaultWidth
115778      * Width of the header if no width or flex is specified. Defaults to 100.
115779      */
115780     defaultWidth: 100,
115781
115782
115783     sortAscText: 'Sort Ascending',
115784     sortDescText: 'Sort Descending',
115785     sortClearText: 'Clear Sort',
115786     columnsText: 'Columns',
115787
115788     lastHeaderCls: Ext.baseCSSPrefix + 'column-header-last',
115789     firstHeaderCls: Ext.baseCSSPrefix + 'column-header-first',
115790     headerOpenCls: Ext.baseCSSPrefix + 'column-header-open',
115791
115792     // private; will probably be removed by 4.0
115793     triStateSort: false,
115794
115795     ddLock: false,
115796
115797     dragging: false,
115798
115799     /**
115800      * <code>true</code> if this HeaderContainer is in fact a group header which contains sub headers.
115801      * @type Boolean
115802      * @property isGroupHeader
115803      */
115804
115805     /**
115806      * @cfg {Boolean} sortable
115807      * Provides the default sortable state for all Headers within this HeaderContainer.
115808      * Also turns on or off the menus in the HeaderContainer. Note that the menu is
115809      * shared across every header and therefore turning it off will remove the menu
115810      * items for every header.
115811      */
115812     sortable: true,
115813
115814     initComponent: function() {
115815         var me = this;
115816
115817         me.headerCounter = 0;
115818         me.plugins = me.plugins || [];
115819
115820         // TODO: Pass in configurations to turn on/off dynamic
115821         //       resizing and disable resizing all together
115822
115823         // Only set up a Resizer and Reorderer for the topmost HeaderContainer.
115824         // Nested Group Headers are themselves HeaderContainers
115825         if (!me.isHeader) {
115826             me.resizer   = Ext.create('Ext.grid.plugin.HeaderResizer');
115827             me.reorderer = Ext.create('Ext.grid.plugin.HeaderReorderer');
115828             if (!me.enableColumnResize) {
115829                 me.resizer.disable();
115830             }
115831             if (!me.enableColumnMove) {
115832                 me.reorderer.disable();
115833             }
115834             me.plugins.push(me.reorderer, me.resizer);
115835         }
115836
115837         // Base headers do not need a box layout
115838         if (me.isHeader && !me.items) {
115839             me.layout = 'auto';
115840         }
115841         // HeaderContainer and Group header needs a gridcolumn layout.
115842         else {
115843             me.layout = {
115844                 type: 'gridcolumn',
115845                 availableSpaceOffset: me.availableSpaceOffset,
115846                 align: 'stretchmax',
115847                 resetStretch: true
115848             };
115849         }
115850         me.defaults = me.defaults || {};
115851         Ext.applyIf(me.defaults, {
115852             width: me.defaultWidth,
115853             triStateSort: me.triStateSort,
115854             sortable: me.sortable
115855         });
115856         me.callParent();
115857         me.addEvents(
115858             /**
115859              * @event columnresize
115860              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115861              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115862              * @param {Number} width
115863              */
115864             'columnresize',
115865
115866             /**
115867              * @event headerclick
115868              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115869              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115870              * @param {Ext.EventObject} e
115871              * @param {HTMLElement} t
115872              */
115873             'headerclick',
115874
115875             /**
115876              * @event headertriggerclick
115877              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115878              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115879              * @param {Ext.EventObject} e
115880              * @param {HTMLElement} t
115881              */
115882             'headertriggerclick',
115883
115884             /**
115885              * @event columnmove
115886              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115887              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115888              * @param {Number} fromIdx
115889              * @param {Number} toIdx
115890              */
115891             'columnmove',
115892             /**
115893              * @event columnhide
115894              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115895              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115896              */
115897             'columnhide',
115898             /**
115899              * @event columnshow
115900              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115901              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115902              */
115903             'columnshow',
115904             /**
115905              * @event sortchange
115906              * @param {Ext.grid.header.Container} ct The grid's header Container which encapsulates all column headers.
115907              * @param {Ext.grid.column.Column} column The Column header Component which provides the column definition
115908              * @param {String} direction
115909              */
115910             'sortchange',
115911             /**
115912              * @event menucreate
115913              * Fired immediately after the column header menu is created.
115914              * @param {Ext.grid.header.Container} ct This instance
115915              * @param {Ext.menu.Menu} menu The Menu that was created
115916              */
115917             'menucreate'
115918         );
115919     },
115920
115921     onDestroy: function() {
115922         Ext.destroy(this.resizer, this.reorderer);
115923         this.callParent();
115924     },
115925     
115926     applyDefaults: function(config){
115927         /*
115928          * Ensure header.Container defaults don't get applied to a RowNumberer 
115929          * if an xtype is supplied. This isn't an ideal solution however it's 
115930          * much more likely that a RowNumberer with no options will be created, 
115931          * wanting to use the defaults specified on the class as opposed to 
115932          * those setup on the Container.
115933          */
115934         if (config && !config.isComponent && config.xtype == 'rownumberer') {
115935             return config;
115936         }
115937         return this.callParent([config]);
115938     },
115939
115940     applyColumnsState: function(columns) {
115941         if (!columns || !columns.length) {
115942             return;
115943         }
115944
115945         var me = this,
115946             i = 0,
115947             index,
115948             col;
115949
115950         Ext.each(columns, function (columnState) {
115951             col = me.down('gridcolumn[headerId=' + columnState.id + ']');
115952             if (col) {
115953                 index = me.items.indexOf(col);
115954                 if (i !== index) {
115955                     me.moveHeader(index, i);
115956                 }
115957
115958                 if (col.applyColumnState) {
115959                     col.applyColumnState(columnState);
115960                 }
115961                 ++i;
115962             }
115963         });
115964     },
115965
115966     getColumnsState: function () {
115967         var me = this,
115968             columns = [],
115969             state;
115970
115971         me.items.each(function (col) {
115972             state = col.getColumnState && col.getColumnState();
115973             if (state) {
115974                 columns.push(state);
115975             }
115976         });
115977
115978         return columns;
115979     },
115980
115981     // Invalidate column cache on add
115982     // We cannot refresh the View on every add because this method is called
115983     // when the HeaderDropZone moves Headers around, that will also refresh the view
115984     onAdd: function(c) {
115985         var me = this;
115986         if (!c.headerId) {
115987             c.headerId = c.initialConfig.id || ('h' + (++me.headerCounter));
115988         }
115989         me.callParent(arguments);
115990         me.purgeCache();
115991     },
115992
115993     // Invalidate column cache on remove
115994     // We cannot refresh the View on every remove because this method is called
115995     // when the HeaderDropZone moves Headers around, that will also refresh the view
115996     onRemove: function(c) {
115997         var me = this;
115998         me.callParent(arguments);
115999         me.purgeCache();
116000     },
116001
116002     afterRender: function() {
116003         this.callParent();
116004         var store   = this.up('[store]').store,
116005             sorters = store.sorters,
116006             first   = sorters.first(),
116007             hd;
116008
116009         if (first) {
116010             hd = this.down('gridcolumn[dataIndex=' + first.property  +']');
116011             if (hd) {
116012                 hd.setSortState(first.direction, false, true);
116013             }
116014         }
116015     },
116016
116017     afterLayout: function() {
116018         if (!this.isHeader) {
116019             var me = this,
116020                 topHeaders = me.query('>gridcolumn:not([hidden])'),
116021                 viewEl,
116022                 firstHeaderEl,
116023                 lastHeaderEl;
116024
116025             me.callParent(arguments);
116026
116027             if (topHeaders.length) {
116028                 firstHeaderEl = topHeaders[0].el;
116029                 if (firstHeaderEl !== me.pastFirstHeaderEl) {
116030                     if (me.pastFirstHeaderEl) {
116031                         me.pastFirstHeaderEl.removeCls(me.firstHeaderCls);
116032                     }
116033                     firstHeaderEl.addCls(me.firstHeaderCls);
116034                     me.pastFirstHeaderEl = firstHeaderEl;
116035                 }
116036
116037                 lastHeaderEl = topHeaders[topHeaders.length - 1].el;
116038                 if (lastHeaderEl !== me.pastLastHeaderEl) {
116039                     if (me.pastLastHeaderEl) {
116040                         me.pastLastHeaderEl.removeCls(me.lastHeaderCls);
116041                     }
116042                     lastHeaderEl.addCls(me.lastHeaderCls);
116043                     me.pastLastHeaderEl = lastHeaderEl;
116044                 }
116045             }
116046         }
116047
116048     },
116049
116050     onHeaderShow: function(header, preventLayout) {
116051         // Pass up to the GridSection
116052         var me = this,
116053             gridSection = me.ownerCt,
116054             menu = me.getMenu(),
116055             topItems, topItemsVisible,
116056             colCheckItem,
116057             itemToEnable,
116058             len, i;
116059
116060         if (menu) {
116061
116062             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
116063             if (colCheckItem) {
116064                 colCheckItem.setChecked(true, true);
116065             }
116066
116067             // There's more than one header visible, and we've disabled some checked items... re-enable them
116068             topItems = menu.query('#columnItem>menucheckitem[checked]');
116069             topItemsVisible = topItems.length;
116070             if ((me.getVisibleGridColumns().length > 1) && me.disabledMenuItems && me.disabledMenuItems.length) {
116071                 if (topItemsVisible == 1) {
116072                     Ext.Array.remove(me.disabledMenuItems, topItems[0]);
116073                 }
116074                 for (i = 0, len = me.disabledMenuItems.length; i < len; i++) {
116075                     itemToEnable = me.disabledMenuItems[i];
116076                     if (!itemToEnable.isDestroyed) {
116077                         itemToEnable[itemToEnable.menu ? 'enableCheckChange' : 'enable']();
116078                     }
116079                 }
116080                 if (topItemsVisible == 1) {
116081                     me.disabledMenuItems = topItems;
116082                 } else {
116083                     me.disabledMenuItems = [];
116084                 }
116085             }
116086         }
116087
116088         // Only update the grid UI when we are notified about base level Header shows;
116089         // Group header shows just cause a layout of the HeaderContainer
116090         if (!header.isGroupHeader) {
116091             if (me.view) {
116092                 me.view.onHeaderShow(me, header, true);
116093             }
116094             if (gridSection) {
116095                 gridSection.onHeaderShow(me, header);
116096             }
116097         }
116098         me.fireEvent('columnshow', me, header);
116099
116100         // The header's own hide suppresses cascading layouts, so lay the headers out now
116101         if (preventLayout !== true) {
116102             me.doLayout();
116103         }
116104     },
116105
116106     doComponentLayout: function(){
116107         var me = this;
116108         if (me.view && me.view.saveScrollState) {
116109             me.view.saveScrollState();
116110         }
116111         me.callParent(arguments);
116112         if (me.view && me.view.restoreScrollState) {
116113             me.view.restoreScrollState();
116114         }
116115     },
116116
116117     onHeaderHide: function(header, suppressLayout) {
116118         // Pass up to the GridSection
116119         var me = this,
116120             gridSection = me.ownerCt,
116121             menu = me.getMenu(),
116122             colCheckItem;
116123
116124         if (menu) {
116125
116126             // If the header was hidden programmatically, sync the Menu state
116127             colCheckItem = menu.down('menucheckitem[headerId=' + header.id + ']');
116128             if (colCheckItem) {
116129                 colCheckItem.setChecked(false, true);
116130             }
116131             me.setDisabledItems();
116132         }
116133
116134         // Only update the UI when we are notified about base level Header hides;
116135         if (!header.isGroupHeader) {
116136             if (me.view) {
116137                 me.view.onHeaderHide(me, header, true);
116138             }
116139             if (gridSection) {
116140                 gridSection.onHeaderHide(me, header);
116141             }
116142
116143             // The header's own hide suppresses cascading layouts, so lay the headers out now
116144             if (!suppressLayout) {
116145                 me.doLayout();
116146             }
116147         }
116148         me.fireEvent('columnhide', me, header);
116149     },
116150
116151     setDisabledItems: function(){
116152         var me = this,
116153             menu = me.getMenu(),
116154             i = 0,
116155             len,
116156             itemsToDisable,
116157             itemToDisable;
116158
116159         // Find what to disable. If only one top level item remaining checked, we have to disable stuff.
116160         itemsToDisable = menu.query('#columnItem>menucheckitem[checked]');
116161         if ((itemsToDisable.length === 1)) {
116162             if (!me.disabledMenuItems) {
116163                 me.disabledMenuItems = [];
116164             }
116165
116166             // If down to only one column visible, also disable any descendant checkitems
116167             if ((me.getVisibleGridColumns().length === 1) && itemsToDisable[0].menu) {
116168                 itemsToDisable = itemsToDisable.concat(itemsToDisable[0].menu.query('menucheckitem[checked]'));
116169             }
116170
116171             len = itemsToDisable.length;
116172             // Disable any further unchecking at any level.
116173             for (i = 0; i < len; i++) {
116174                 itemToDisable = itemsToDisable[i];
116175                 if (!Ext.Array.contains(me.disabledMenuItems, itemToDisable)) {
116176
116177                     // If we only want to disable check change: it might be a disabled item, so enable it prior to
116178                     // setting its correct disablement level.
116179                     itemToDisable.disabled = false;
116180                     itemToDisable[itemToDisable.menu ? 'disableCheckChange' : 'disable']();
116181                     me.disabledMenuItems.push(itemToDisable);
116182                 }
116183             }
116184         }
116185     },
116186
116187     /**
116188      * Temporarily lock the headerCt. This makes it so that clicking on headers
116189      * don't trigger actions like sorting or opening of the header menu. This is
116190      * done because extraneous events may be fired on the headers after interacting
116191      * with a drag drop operation.
116192      * @private
116193      */
116194     tempLock: function() {
116195         this.ddLock = true;
116196         Ext.Function.defer(function() {
116197             this.ddLock = false;
116198         }, 200, this);
116199     },
116200
116201     onHeaderResize: function(header, w, suppressFocus) {
116202         this.tempLock();
116203         if (this.view && this.view.rendered) {
116204             this.view.onHeaderResize(header, w, suppressFocus);
116205         }
116206     },
116207
116208     onHeaderClick: function(header, e, t) {
116209         this.fireEvent("headerclick", this, header, e, t);
116210     },
116211
116212     onHeaderTriggerClick: function(header, e, t) {
116213         // generate and cache menu, provide ability to cancel/etc
116214         if (this.fireEvent("headertriggerclick", this, header, e, t) !== false) {
116215             this.showMenuBy(t, header);
116216         }
116217     },
116218
116219     showMenuBy: function(t, header) {
116220         var menu = this.getMenu(),
116221             ascItem  = menu.down('#ascItem'),
116222             descItem = menu.down('#descItem'),
116223             sortableMth;
116224
116225         menu.activeHeader = menu.ownerCt = header;
116226         menu.setFloatParent(header);
116227         // TODO: remove coupling to Header's titleContainer el
116228         header.titleContainer.addCls(this.headerOpenCls);
116229
116230         // enable or disable asc & desc menu items based on header being sortable
116231         sortableMth = header.sortable ? 'enable' : 'disable';
116232         if (ascItem) {
116233             ascItem[sortableMth]();
116234         }
116235         if (descItem) {
116236             descItem[sortableMth]();
116237         }
116238         menu.showBy(t);
116239     },
116240
116241     // remove the trigger open class when the menu is hidden
116242     onMenuDeactivate: function() {
116243         var menu = this.getMenu();
116244         // TODO: remove coupling to Header's titleContainer el
116245         menu.activeHeader.titleContainer.removeCls(this.headerOpenCls);
116246     },
116247
116248     moveHeader: function(fromIdx, toIdx) {
116249
116250         // An automatically expiring lock
116251         this.tempLock();
116252         this.onHeaderMoved(this.move(fromIdx, toIdx), fromIdx, toIdx);
116253     },
116254
116255     purgeCache: function() {
116256         var me = this;
116257         // Delete column cache - column order has changed.
116258         delete me.gridDataColumns;
116259         delete me.hideableColumns;
116260
116261         // Menu changes when columns are moved. It will be recreated.
116262         if (me.menu) {
116263             me.menu.destroy();
116264             delete me.menu;
116265         }
116266     },
116267
116268     onHeaderMoved: function(header, fromIdx, toIdx) {
116269         var me = this,
116270             gridSection = me.ownerCt;
116271
116272         if (gridSection && gridSection.onHeaderMove) {
116273             gridSection.onHeaderMove(me, header, fromIdx, toIdx);
116274         }
116275         me.fireEvent("columnmove", me, header, fromIdx, toIdx);
116276     },
116277
116278     /**
116279      * Gets the menu (and will create it if it doesn't already exist)
116280      * @private
116281      */
116282     getMenu: function() {
116283         var me = this;
116284
116285         if (!me.menu) {
116286             me.menu = Ext.create('Ext.menu.Menu', {
116287                 hideOnParentHide: false,  // Persists when owning ColumnHeader is hidden
116288                 items: me.getMenuItems(),
116289                 listeners: {
116290                     deactivate: me.onMenuDeactivate,
116291                     scope: me
116292                 }
116293             });
116294             me.setDisabledItems();
116295             me.fireEvent('menucreate', me, me.menu);
116296         }
116297         return me.menu;
116298     },
116299
116300     /**
116301      * Returns an array of menu items to be placed into the shared menu
116302      * across all headers in this header container.
116303      * @returns {Array} menuItems
116304      */
116305     getMenuItems: function() {
116306         var me = this,
116307             menuItems = [],
116308             hideableColumns = me.enableColumnHide ? me.getColumnMenu(me) : null;
116309
116310         if (me.sortable) {
116311             menuItems = [{
116312                 itemId: 'ascItem',
116313                 text: me.sortAscText,
116314                 cls: Ext.baseCSSPrefix + 'hmenu-sort-asc',
116315                 handler: me.onSortAscClick,
116316                 scope: me
116317             },{
116318                 itemId: 'descItem',
116319                 text: me.sortDescText,
116320                 cls: Ext.baseCSSPrefix + 'hmenu-sort-desc',
116321                 handler: me.onSortDescClick,
116322                 scope: me
116323             }];
116324         }
116325         if (hideableColumns && hideableColumns.length) {
116326             menuItems.push('-', {
116327                 itemId: 'columnItem',
116328                 text: me.columnsText,
116329                 cls: Ext.baseCSSPrefix + 'cols-icon',
116330                 menu: hideableColumns
116331             });
116332         }
116333         return menuItems;
116334     },
116335
116336     // sort asc when clicking on item in menu
116337     onSortAscClick: function() {
116338         var menu = this.getMenu(),
116339             activeHeader = menu.activeHeader;
116340
116341         activeHeader.setSortState('ASC');
116342     },
116343
116344     // sort desc when clicking on item in menu
116345     onSortDescClick: function() {
116346         var menu = this.getMenu(),
116347             activeHeader = menu.activeHeader;
116348
116349         activeHeader.setSortState('DESC');
116350     },
116351
116352     /**
116353      * Returns an array of menu CheckItems corresponding to all immediate children of the passed Container which have been configured as hideable.
116354      */
116355     getColumnMenu: function(headerContainer) {
116356         var menuItems = [],
116357             i = 0,
116358             item,
116359             items = headerContainer.query('>gridcolumn[hideable]'),
116360             itemsLn = items.length,
116361             menuItem;
116362
116363         for (; i < itemsLn; i++) {
116364             item = items[i];
116365             menuItem = Ext.create('Ext.menu.CheckItem', {
116366                 text: item.text,
116367                 checked: !item.hidden,
116368                 hideOnClick: false,
116369                 headerId: item.id,
116370                 menu: item.isGroupHeader ? this.getColumnMenu(item) : undefined,
116371                 checkHandler: this.onColumnCheckChange,
116372                 scope: this
116373             });
116374             if (itemsLn === 1) {
116375                 menuItem.disabled = true;
116376             }
116377             menuItems.push(menuItem);
116378
116379             // If the header is ever destroyed - for instance by dragging out the last remaining sub header,
116380             // then the associated menu item must also be destroyed.
116381             item.on({
116382                 destroy: Ext.Function.bind(menuItem.destroy, menuItem)
116383             });
116384         }
116385         return menuItems;
116386     },
116387
116388     onColumnCheckChange: function(checkItem, checked) {
116389         var header = Ext.getCmp(checkItem.headerId);
116390         header[checked ? 'show' : 'hide']();
116391     },
116392
116393     /**
116394      * Get the columns used for generating a template via TableChunker.
116395      * Returns an array of all columns and their
116396      *  - dataIndex
116397      *  - align
116398      *  - width
116399      *  - id
116400      *  - columnId - used to create an identifying CSS class
116401      *  - cls The tdCls configuration from the Column object
116402      *  @private
116403      */
116404     getColumnsForTpl: function(flushCache) {
116405         var cols    = [],
116406             headers   = this.getGridColumns(flushCache),
116407             headersLn = headers.length,
116408             i = 0,
116409             header,
116410             width;
116411
116412         for (; i < headersLn; i++) {
116413             header = headers[i];
116414
116415             if (header.hidden || header.up('headercontainer[hidden=true]')) {
116416                 width = 0;
116417             } else {
116418                 width = header.getDesiredWidth();
116419                 // IE6 and IE7 bug.
116420                 // Setting the width of the first TD does not work - ends up with a 1 pixel discrepancy.
116421                 // We need to increment the passed with in this case.
116422                 if ((i === 0) && (Ext.isIE6 || Ext.isIE7)) {
116423                     width += 1;
116424                 }
116425             }
116426             cols.push({
116427                 dataIndex: header.dataIndex,
116428                 align: header.align,
116429                 width: width,
116430                 id: header.id,
116431                 cls: header.tdCls,
116432                 columnId: header.getItemId()
116433             });
116434         }
116435         return cols;
116436     },
116437
116438     /**
116439      * Returns the number of <b>grid columns</b> descended from this HeaderContainer.
116440      * Group Columns are HeaderContainers. All grid columns are returned, including hidden ones.
116441      */
116442     getColumnCount: function() {
116443         return this.getGridColumns().length;
116444     },
116445
116446     /**
116447      * Gets the full width of all columns that are visible.
116448      */
116449     getFullWidth: function(flushCache) {
116450         var fullWidth = 0,
116451             headers     = this.getVisibleGridColumns(flushCache),
116452             headersLn   = headers.length,
116453             i         = 0;
116454
116455         for (; i < headersLn; i++) {
116456             if (!isNaN(headers[i].width)) {
116457                 // use headers getDesiredWidth if its there
116458                 if (headers[i].getDesiredWidth) {
116459                     fullWidth += headers[i].getDesiredWidth();
116460                 // if injected a diff cmp use getWidth
116461                 } else {
116462                     fullWidth += headers[i].getWidth();
116463                 }
116464             }
116465         }
116466         return fullWidth;
116467     },
116468
116469     // invoked internally by a header when not using triStateSorting
116470     clearOtherSortStates: function(activeHeader) {
116471         var headers   = this.getGridColumns(),
116472             headersLn = headers.length,
116473             i         = 0,
116474             oldSortState;
116475
116476         for (; i < headersLn; i++) {
116477             if (headers[i] !== activeHeader) {
116478                 oldSortState = headers[i].sortState;
116479                 // unset the sortstate and dont recurse
116480                 headers[i].setSortState(null, true);
116481                 //if (!silent && oldSortState !== null) {
116482                 //    this.fireEvent('sortchange', this, headers[i], null);
116483                 //}
116484             }
116485         }
116486     },
116487
116488     /**
116489      * Returns an array of the <b>visible</b> columns in the grid. This goes down to the lowest column header
116490      * level, and does not return <i>grouped</i> headers which contain sub headers.
116491      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
116492      * @returns {Array}
116493      */
116494     getVisibleGridColumns: function(refreshCache) {
116495         return Ext.ComponentQuery.query(':not([hidden])', this.getGridColumns(refreshCache));
116496     },
116497
116498     /**
116499      * Returns an array of all columns which map to Store fields. This goes down to the lowest column header
116500      * level, and does not return <i>grouped</i> headers which contain sub headers.
116501      * @param {Boolean} refreshCache If omitted, the cached set of columns will be returned. Pass true to refresh the cache.
116502      * @returns {Array}
116503      */
116504     getGridColumns: function(refreshCache) {
116505         var me = this,
116506             result = refreshCache ? null : me.gridDataColumns;
116507
116508         // Not already got the column cache, so collect the base columns
116509         if (!result) {
116510             me.gridDataColumns = result = [];
116511             me.cascade(function(c) {
116512                 if ((c !== me) && !c.isGroupHeader) {
116513                     result.push(c);
116514                 }
116515             });
116516         }
116517
116518         return result;
116519     },
116520
116521     /**
116522      * @private
116523      * For use by column headers in determining whether there are any hideable columns when deciding whether or not
116524      * the header menu should be disabled.
116525      */
116526     getHideableColumns: function(refreshCache) {
116527         var me = this,
116528             result = refreshCache ? null : me.hideableColumns;
116529
116530         if (!result) {
116531             result = me.hideableColumns = me.query('[hideable]');
116532         }
116533         return result;
116534     },
116535
116536     /**
116537      * Get the index of a leaf level header regardless of what the nesting
116538      * structure is.
116539      */
116540     getHeaderIndex: function(header) {
116541         var columns = this.getGridColumns();
116542         return Ext.Array.indexOf(columns, header);
116543     },
116544
116545     /**
116546      * Get a leaf level header by index regardless of what the nesting
116547      * structure is.
116548      */
116549     getHeaderAtIndex: function(index) {
116550         var columns = this.getGridColumns();
116551         return columns[index];
116552     },
116553
116554     /**
116555      * Maps the record data to base it on the header id's.
116556      * This correlates to the markup/template generated by
116557      * TableChunker.
116558      */
116559     prepareData: function(data, rowIdx, record, view, panel) {
116560         var obj       = {},
116561             headers   = this.gridDataColumns || this.getGridColumns(),
116562             headersLn = headers.length,
116563             colIdx    = 0,
116564             header,
116565             headerId,
116566             renderer,
116567             value,
116568             metaData,
116569             store = panel.store;
116570
116571         for (; colIdx < headersLn; colIdx++) {
116572             metaData = {
116573                 tdCls: '',
116574                 style: ''
116575             };
116576             header = headers[colIdx];
116577             headerId = header.id;
116578             renderer = header.renderer;
116579             value = data[header.dataIndex];
116580
116581             // When specifying a renderer as a string, it always resolves
116582             // to Ext.util.Format
116583             if (typeof renderer === "string") {
116584                 header.renderer = renderer = Ext.util.Format[renderer];
116585             }
116586
116587             if (typeof renderer === "function") {
116588                 value = renderer.call(
116589                     header.scope || this.ownerCt,
116590                     value,
116591                     // metadata per cell passing an obj by reference so that
116592                     // it can be manipulated inside the renderer
116593                     metaData,
116594                     record,
116595                     rowIdx,
116596                     colIdx,
116597                     store,
116598                     view
116599                 );
116600             }
116601
116602
116603             obj[headerId+'-modified'] = record.isModified(header.dataIndex) ? Ext.baseCSSPrefix + 'grid-dirty-cell' : '';
116604             obj[headerId+'-tdCls'] = metaData.tdCls;
116605             obj[headerId+'-tdAttr'] = metaData.tdAttr;
116606             obj[headerId+'-style'] = metaData.style;
116607             if (value === undefined || value === null || value === '') {
116608                 value = '&#160;';
116609             }
116610             obj[headerId] = value;
116611         }
116612         return obj;
116613     },
116614
116615     expandToFit: function(header) {
116616         if (this.view) {
116617             this.view.expandToFit(header);
116618         }
116619     }
116620 });
116621
116622 /**
116623  * This class specifies the definition for a column inside a {@link Ext.grid.Panel}. It encompasses
116624  * both the grid header configuration as well as displaying data within the grid itself. If the
116625  * {@link #columns} configuration is specified, this column will become a column group and can
116626  * contain other columns inside. In general, this class will not be created directly, rather
116627  * an array of column configurations will be passed to the grid:
116628  *
116629  *     @example
116630  *     Ext.create('Ext.data.Store', {
116631  *         storeId:'employeeStore',
116632  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
116633  *         data:[
116634  *             {firstname:"Michael", lastname:"Scott", senority:7, dep:"Manangement", hired:"01/10/2004"},
116635  *             {firstname:"Dwight", lastname:"Schrute", senority:2, dep:"Sales", hired:"04/01/2004"},
116636  *             {firstname:"Jim", lastname:"Halpert", senority:3, dep:"Sales", hired:"02/22/2006"},
116637  *             {firstname:"Kevin", lastname:"Malone", senority:4, dep:"Accounting", hired:"06/10/2007"},
116638  *             {firstname:"Angela", lastname:"Martin", senority:5, dep:"Accounting", hired:"10/21/2008"}
116639  *         ]
116640  *     });
116641  *
116642  *     Ext.create('Ext.grid.Panel', {
116643  *         title: 'Column Demo',
116644  *         store: Ext.data.StoreManager.lookup('employeeStore'),
116645  *         columns: [
116646  *             {text: 'First Name',  dataIndex:'firstname'},
116647  *             {text: 'Last Name',  dataIndex:'lastname'},
116648  *             {text: 'Hired Month',  dataIndex:'hired', xtype:'datecolumn', format:'M'},
116649  *             {text: 'Department (Yrs)', xtype:'templatecolumn', tpl:'{dep} ({senority})'}
116650  *         ],
116651  *         width: 400,
116652  *         renderTo: Ext.getBody()
116653  *     });
116654  *
116655  * # Convenience Subclasses
116656  *
116657  * There are several column subclasses that provide default rendering for various data types
116658  *
116659  *  - {@link Ext.grid.column.Action}: Renders icons that can respond to click events inline
116660  *  - {@link Ext.grid.column.Boolean}: Renders for boolean values
116661  *  - {@link Ext.grid.column.Date}: Renders for date values
116662  *  - {@link Ext.grid.column.Number}: Renders for numeric values
116663  *  - {@link Ext.grid.column.Template}: Renders a value using an {@link Ext.XTemplate} using the record data
116664  *
116665  * # Setting Sizes
116666  *
116667  * The columns are laid out by a {@link Ext.layout.container.HBox} layout, so a column can either
116668  * be given an explicit width value or a flex configuration. If no width is specified the grid will
116669  * automatically the size the column to 100px. For column groups, the size is calculated by measuring
116670  * the width of the child columns, so a width option should not be specified in that case.
116671  *
116672  * # Header Options
116673  *
116674  *  - {@link #text}: Sets the header text for the column
116675  *  - {@link #sortable}: Specifies whether the column can be sorted by clicking the header or using the column menu
116676  *  - {@link #hideable}: Specifies whether the column can be hidden using the column menu
116677  *  - {@link #menuDisabled}: Disables the column header menu
116678  *  - {@link #draggable}: Specifies whether the column header can be reordered by dragging
116679  *  - {@link #groupable}: Specifies whether the grid can be grouped by the column dataIndex. See also {@link Ext.grid.feature.Grouping}
116680  *
116681  * # Data Options
116682  *
116683  *  - {@link #dataIndex}: The dataIndex is the field in the underlying {@link Ext.data.Store} to use as the value for the column.
116684  *  - {@link #renderer}: Allows the underlying store value to be transformed before being displayed in the grid
116685  */
116686 Ext.define('Ext.grid.column.Column', {
116687     extend: 'Ext.grid.header.Container',
116688     alias: 'widget.gridcolumn',
116689     requires: ['Ext.util.KeyNav'],
116690     alternateClassName: 'Ext.grid.Column',
116691
116692     baseCls: Ext.baseCSSPrefix + 'column-header ' + Ext.baseCSSPrefix + 'unselectable',
116693
116694     // Not the standard, automatically applied overCls because we must filter out overs of child headers.
116695     hoverCls: Ext.baseCSSPrefix + 'column-header-over',
116696
116697     handleWidth: 5,
116698
116699     sortState: null,
116700
116701     possibleSortStates: ['ASC', 'DESC'],
116702
116703     renderTpl:
116704         '<div id="{id}-titleContainer" class="' + Ext.baseCSSPrefix + 'column-header-inner">' +
116705             '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'column-header-text">' +
116706                 '{text}' +
116707             '</span>' +
116708             '<tpl if="!values.menuDisabled">'+
116709                 '<div id="{id}-triggerEl" class="' + Ext.baseCSSPrefix + 'column-header-trigger"></div>'+
116710             '</tpl>' +
116711         '</div>',
116712
116713     /**
116714      * @cfg {Object[]} columns
116715      * An optional array of sub-column definitions. This column becomes a group, and houses the columns defined in the
116716      * `columns` config.
116717      *
116718      * Group columns may not be sortable. But they may be hideable and moveable. And you may move headers into and out
116719      * of a group. Note that if all sub columns are dragged out of a group, the group is destroyed.
116720      */
116721
116722     /**
116723      * @cfg {String} dataIndex
116724      * The name of the field in the grid's {@link Ext.data.Store}'s {@link Ext.data.Model} definition from
116725      * which to draw the column's value. **Required.**
116726      */
116727     dataIndex: null,
116728
116729     /**
116730      * @cfg {String} text
116731      * The header text to be used as innerHTML (html tags are accepted) to display in the Grid.
116732      * **Note**: to have a clickable header with no text displayed you can use the default of `&#160;` aka `&nbsp;`.
116733      */
116734     text: '&#160;',
116735
116736     /**
116737      * @cfg {Boolean} sortable
116738      * False to disable sorting of this column. Whether local/remote sorting is used is specified in
116739      * `{@link Ext.data.Store#remoteSort}`. Defaults to true.
116740      */
116741     sortable: true,
116742
116743     /**
116744      * @cfg {Boolean} groupable
116745      * If the grid uses a {@link Ext.grid.feature.Grouping}, this option may be used to disable the header menu
116746      * item to group by the column selected. By default, the header menu group option is enabled. Set to false to
116747      * disable (but still show) the group option in the header menu for the column.
116748      */
116749
116750     /**
116751      * @cfg {Boolean} fixed
116752      * @deprecated.
116753      * True to prevent the column from being resizable.
116754      */
116755
116756     /**
116757      * @cfg {Boolean} resizable
116758      * Set to <code>false</code> to prevent the column from being resizable. Defaults to <code>true</code>
116759      */
116760
116761     /**
116762      * @cfg {Boolean} hideable
116763      * False to prevent the user from hiding this column. Defaults to true.
116764      */
116765     hideable: true,
116766
116767     /**
116768      * @cfg {Boolean} menuDisabled
116769      * True to disable the column header menu containing sort/hide options. Defaults to false.
116770      */
116771     menuDisabled: false,
116772
116773     /**
116774      * @cfg {Function} renderer
116775      * A renderer is an 'interceptor' method which can be used transform data (value, appearance, etc.)
116776      * before it is rendered. Example:
116777      *
116778      *     {
116779      *         renderer: function(value){
116780      *             if (value === 1) {
116781      *                 return '1 person';
116782      *             }
116783      *             return value + ' people';
116784      *         }
116785      *     }
116786      *
116787      * @cfg {Object} renderer.value The data value for the current cell
116788      * @cfg {Object} renderer.metaData A collection of metadata about the current cell; can be used or modified
116789      * by the renderer. Recognized properties are: tdCls, tdAttr, and style.
116790      * @cfg {Ext.data.Model} renderer.record The record for the current row
116791      * @cfg {Number} renderer.rowIndex The index of the current row
116792      * @cfg {Number} renderer.colIndex The index of the current column
116793      * @cfg {Ext.data.Store} renderer.store The data store
116794      * @cfg {Ext.view.View} renderer.view The current view
116795      * @cfg {String} renderer.return The HTML string to be rendered.
116796      */
116797     renderer: false,
116798
116799     /**
116800      * @cfg {String} align
116801      * Sets the alignment of the header and rendered columns. Defaults to 'left'.
116802      */
116803     align: 'left',
116804
116805     /**
116806      * @cfg {Boolean} draggable
116807      * False to disable drag-drop reordering of this column. Defaults to true.
116808      */
116809     draggable: true,
116810
116811     // Header does not use the typical ComponentDraggable class and therefore we
116812     // override this with an emptyFn. It is controlled at the HeaderDragZone.
116813     initDraggable: Ext.emptyFn,
116814
116815     /**
116816      * @cfg {String} tdCls
116817      * A CSS class names to apply to the table cells for this column.
116818      */
116819
116820     /**
116821      * @cfg {Object/String} editor
116822      * An optional xtype or config object for a {@link Ext.form.field.Field Field} to use for editing.
116823      * Only applicable if the grid is using an {@link Ext.grid.plugin.Editing Editing} plugin.
116824      */
116825
116826     /**
116827      * @cfg {Object/String} field
116828      * Alias for {@link #editor}.
116829      * @deprecated 4.0.5 Use {@link #editor} instead.
116830      */
116831
116832     /**
116833      * @property {Ext.Element} triggerEl
116834      * Element that acts as button for column header dropdown menu.
116835      */
116836
116837     /**
116838      * @property {Ext.Element} textEl
116839      * Element that contains the text in column header.
116840      */
116841
116842     /**
116843      * @private
116844      * Set in this class to identify, at runtime, instances which are not instances of the
116845      * HeaderContainer base class, but are in fact, the subclass: Header.
116846      */
116847     isHeader: true,
116848
116849     initComponent: function() {
116850         var me = this,
116851             i,
116852             len,
116853             item;
116854
116855         if (Ext.isDefined(me.header)) {
116856             me.text = me.header;
116857             delete me.header;
116858         }
116859
116860         // Flexed Headers need to have a minWidth defined so that they can never be squeezed out of existence by the
116861         // HeaderContainer's specialized Box layout, the ColumnLayout. The ColumnLayout's overridden calculateChildboxes
116862         // method extends the available layout space to accommodate the "desiredWidth" of all the columns.
116863         if (me.flex) {
116864             me.minWidth = me.minWidth || Ext.grid.plugin.HeaderResizer.prototype.minColWidth;
116865         }
116866         // Non-flexed Headers may never be squeezed in the event of a shortfall so
116867         // always set their minWidth to their current width.
116868         else {
116869             me.minWidth = me.width;
116870         }
116871
116872         if (!me.triStateSort) {
116873             me.possibleSortStates.length = 2;
116874         }
116875
116876         // A group header; It contains items which are themselves Headers
116877         if (Ext.isDefined(me.columns)) {
116878             me.isGroupHeader = true;
116879
116880
116881             // The headers become child items
116882             me.items = me.columns;
116883             delete me.columns;
116884             delete me.flex;
116885             me.width = 0;
116886
116887             // Acquire initial width from sub headers
116888             for (i = 0, len = me.items.length; i < len; i++) {
116889                 item = me.items[i];
116890                 if (!item.hidden) {
116891                     me.width += item.width || Ext.grid.header.Container.prototype.defaultWidth;
116892                 }
116893             }
116894             me.minWidth = me.width;
116895
116896             me.cls = (me.cls||'') + ' ' + Ext.baseCSSPrefix + 'group-header';
116897             me.sortable = false;
116898             me.resizable = false;
116899             me.align = 'center';
116900         }
116901
116902         me.addChildEls('titleContainer', 'triggerEl', 'textEl');
116903
116904         // Initialize as a HeaderContainer
116905         me.callParent(arguments);
116906     },
116907
116908     onAdd: function(childHeader) {
116909         childHeader.isSubHeader = true;
116910         childHeader.addCls(Ext.baseCSSPrefix + 'group-sub-header');
116911         this.callParent(arguments);
116912     },
116913
116914     onRemove: function(childHeader) {
116915         childHeader.isSubHeader = false;
116916         childHeader.removeCls(Ext.baseCSSPrefix + 'group-sub-header');
116917         this.callParent(arguments);
116918     },
116919
116920     initRenderData: function() {
116921         var me = this;
116922
116923         Ext.applyIf(me.renderData, {
116924             text: me.text,
116925             menuDisabled: me.menuDisabled
116926         });
116927         return me.callParent(arguments);
116928     },
116929
116930     applyColumnState: function (state) {
116931         var me = this,
116932             defined = Ext.isDefined;
116933             
116934         // apply any columns
116935         me.applyColumnsState(state.columns);
116936
116937         // Only state properties which were saved should be restored.
116938         // (Only user-changed properties were saved by getState)
116939         if (defined(state.hidden)) {
116940             me.hidden = state.hidden;
116941         }
116942         if (defined(state.locked)) {
116943             me.locked = state.locked;
116944         }
116945         if (defined(state.sortable)) {
116946             me.sortable = state.sortable;
116947         }
116948         if (defined(state.width)) {
116949             delete me.flex;
116950             me.width = state.width;
116951         } else if (defined(state.flex)) {
116952             delete me.width;
116953             me.flex = state.flex;
116954         }
116955     },
116956
116957     getColumnState: function () {
116958         var me = this,
116959             columns = [],
116960             state = {
116961                 id: me.headerId
116962             };
116963
116964         me.savePropsToState(['hidden', 'sortable', 'locked', 'flex', 'width'], state);
116965         
116966         if (me.isGroupHeader) {
116967             me.items.each(function(column){
116968                 columns.push(column.getColumnState());
116969             });
116970             if (columns.length) {
116971                 state.columns = columns;
116972             }
116973         } else if (me.isSubHeader && me.ownerCt.hidden) {
116974             // don't set hidden on the children so they can auto height
116975             delete me.hidden;
116976         }
116977
116978         if ('width' in state) {
116979             delete state.flex; // width wins
116980         }
116981         return state;
116982     },
116983
116984     /**
116985      * Sets the header text for this Column.
116986      * @param {String} text The header to display on this Column.
116987      */
116988     setText: function(text) {
116989         this.text = text;
116990         if (this.rendered) {
116991             this.textEl.update(text);
116992         }
116993     },
116994
116995     // Find the topmost HeaderContainer: An ancestor which is NOT a Header.
116996     // Group Headers are themselves HeaderContainers
116997     getOwnerHeaderCt: function() {
116998         return this.up(':not([isHeader])');
116999     },
117000
117001     /**
117002      * Returns the true grid column index associated with this column only if this column is a base level Column. If it
117003      * is a group column, it returns `false`.
117004      * @return {Number}
117005      */
117006     getIndex: function() {
117007         return this.isGroupColumn ? false : this.getOwnerHeaderCt().getHeaderIndex(this);
117008     },
117009
117010     onRender: function() {
117011         var me = this,
117012             grid = me.up('tablepanel');
117013
117014         // Disable the menu if there's nothing to show in the menu, ie:
117015         // Column cannot be sorted, grouped or locked, and there are no grid columns which may be hidden
117016         if (grid && (!me.sortable || grid.sortableColumns === false) && !me.groupable && !me.lockable && (grid.enableColumnHide === false || !me.getOwnerHeaderCt().getHideableColumns().length)) {
117017             me.menuDisabled = true;
117018         }
117019         me.callParent(arguments);
117020     },
117021
117022     afterRender: function() {
117023         var me = this,
117024             el = me.el;
117025
117026         me.callParent(arguments);
117027
117028         el.addCls(Ext.baseCSSPrefix + 'column-header-align-' + me.align).addClsOnOver(me.overCls);
117029
117030         me.mon(el, {
117031             click:     me.onElClick,
117032             dblclick:  me.onElDblClick,
117033             scope:     me
117034         });
117035
117036         // BrowserBug: Ie8 Strict Mode, this will break the focus for this browser,
117037         // must be fixed when focus management will be implemented.
117038         if (!Ext.isIE8 || !Ext.isStrict) {
117039             me.mon(me.getFocusEl(), {
117040                 focus: me.onTitleMouseOver,
117041                 blur: me.onTitleMouseOut,
117042                 scope: me
117043             });
117044         }
117045
117046         me.mon(me.titleContainer, {
117047             mouseenter:  me.onTitleMouseOver,
117048             mouseleave:  me.onTitleMouseOut,
117049             scope:      me
117050         });
117051
117052         me.keyNav = Ext.create('Ext.util.KeyNav', el, {
117053             enter: me.onEnterKey,
117054             down: me.onDownKey,
117055             scope: me
117056         });
117057     },
117058
117059     /**
117060      * Sets the width of this Column.
117061      * @param {Number} width New width.
117062      */
117063     setWidth: function(width, /* private - used internally */ doLayout) {
117064         var me = this,
117065             headerCt = me.ownerCt,
117066             siblings,
117067             len, i,
117068             oldWidth = me.getWidth(),
117069             groupWidth = 0,
117070             sibling;
117071
117072         if (width !== oldWidth) {
117073             me.oldWidth = oldWidth;
117074
117075             // Non-flexed Headers may never be squeezed in the event of a shortfall so
117076             // always set the minWidth to their current width.
117077             me.minWidth = me.width = width;
117078
117079             // Bubble size changes upwards to group headers
117080             if (headerCt.isGroupHeader) {
117081                 siblings = headerCt.items.items;
117082                 len = siblings.length;
117083
117084                 for (i = 0; i < len; i++) {
117085                     sibling = siblings[i];
117086                     if (!sibling.hidden) {
117087                         groupWidth += (sibling === me) ? width : sibling.getWidth();
117088                     }
117089                 }
117090                 headerCt.setWidth(groupWidth, doLayout);
117091             } else if (doLayout !== false) {
117092                 // Allow the owning Container to perform the sizing
117093                 headerCt.doLayout();
117094             }
117095         }
117096     },
117097
117098     afterComponentLayout: function(width, height) {
117099         var me = this,
117100             ownerHeaderCt = this.getOwnerHeaderCt();
117101
117102         me.callParent(arguments);
117103
117104         // Only changes at the base level inform the grid's HeaderContainer which will update the View
117105         // Skip this if the width is null or undefined which will be the Box layout's initial pass  through the child Components
117106         // Skip this if it's the initial size setting in which case there is no ownerheaderCt yet - that is set afterRender
117107         if (width && !me.isGroupHeader && ownerHeaderCt) {
117108             ownerHeaderCt.onHeaderResize(me, width, true);
117109         }
117110         if (me.oldWidth && (width !== me.oldWidth)) {
117111             ownerHeaderCt.fireEvent('columnresize', ownerHeaderCt, this, width);
117112         }
117113         delete me.oldWidth;
117114     },
117115
117116     // private
117117     // After the container has laid out and stretched, it calls this to correctly pad the inner to center the text vertically
117118     // Total available header height must be passed to enable padding for inner elements to be calculated.
117119     setPadding: function(headerHeight) {
117120         var me = this,
117121             lineHeight = Ext.util.TextMetrics.measure(me.textEl.dom, me.text).height;
117122
117123         // Top title containing element must stretch to match height of sibling group headers
117124         if (!me.isGroupHeader) {
117125             if (me.titleContainer.getHeight() < headerHeight) {
117126                 me.titleContainer.dom.style.height = headerHeight + 'px';
117127             }
117128         }
117129         headerHeight = me.titleContainer.getViewSize().height;
117130
117131         // Vertically center the header text in potentially vertically stretched header
117132         if (lineHeight) {
117133             me.titleContainer.setStyle({
117134                 paddingTop: Math.max(((headerHeight - lineHeight) / 2), 0) + 'px'
117135             });
117136         }
117137
117138         // Only IE needs this
117139         if (Ext.isIE && me.triggerEl) {
117140             me.triggerEl.setHeight(headerHeight);
117141         }
117142     },
117143
117144     onDestroy: function() {
117145         var me = this;
117146         // force destroy on the textEl, IE reports a leak
117147         Ext.destroy(me.textEl, me.keyNav);
117148         delete me.keyNav;
117149         me.callParent(arguments);
117150     },
117151
117152     onTitleMouseOver: function() {
117153         this.titleContainer.addCls(this.hoverCls);
117154     },
117155
117156     onTitleMouseOut: function() {
117157         this.titleContainer.removeCls(this.hoverCls);
117158     },
117159
117160     onDownKey: function(e) {
117161         if (this.triggerEl) {
117162             this.onElClick(e, this.triggerEl.dom || this.el.dom);
117163         }
117164     },
117165
117166     onEnterKey: function(e) {
117167         this.onElClick(e, this.el.dom);
117168     },
117169
117170     /**
117171      * @private
117172      * Double click
117173      * @param e
117174      * @param t
117175      */
117176     onElDblClick: function(e, t) {
117177         var me = this,
117178             ownerCt = me.ownerCt;
117179         if (ownerCt && Ext.Array.indexOf(ownerCt.items, me) !== 0 && me.isOnLeftEdge(e) ) {
117180             ownerCt.expandToFit(me.previousSibling('gridcolumn'));
117181         }
117182     },
117183
117184     onElClick: function(e, t) {
117185
117186         // The grid's docked HeaderContainer.
117187         var me = this,
117188             ownerHeaderCt = me.getOwnerHeaderCt();
117189
117190         if (ownerHeaderCt && !ownerHeaderCt.ddLock) {
117191             // Firefox doesn't check the current target in a within check.
117192             // Therefore we check the target directly and then within (ancestors)
117193             if (me.triggerEl && (e.target === me.triggerEl.dom || t === me.triggerEl.dom || e.within(me.triggerEl))) {
117194                 ownerHeaderCt.onHeaderTriggerClick(me, e, t);
117195             // if its not on the left hand edge, sort
117196             } else if (e.getKey() || (!me.isOnLeftEdge(e) && !me.isOnRightEdge(e))) {
117197                 me.toggleSortState();
117198                 ownerHeaderCt.onHeaderClick(me, e, t);
117199             }
117200         }
117201     },
117202
117203     /**
117204      * @private
117205      * Process UI events from the view. The owning TablePanel calls this method, relaying events from the TableView
117206      * @param {String} type Event type, eg 'click'
117207      * @param {Ext.view.Table} view TableView Component
117208      * @param {HTMLElement} cell Cell HtmlElement the event took place within
117209      * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
117210      * @param {Number} cellIndex Cell index within the row
117211      * @param {Ext.EventObject} e Original event
117212      */
117213     processEvent: function(type, view, cell, recordIndex, cellIndex, e) {
117214         return this.fireEvent.apply(this, arguments);
117215     },
117216
117217     toggleSortState: function() {
117218         var me = this,
117219             idx,
117220             nextIdx;
117221
117222         if (me.sortable) {
117223             idx = Ext.Array.indexOf(me.possibleSortStates, me.sortState);
117224
117225             nextIdx = (idx + 1) % me.possibleSortStates.length;
117226             me.setSortState(me.possibleSortStates[nextIdx]);
117227         }
117228     },
117229
117230     doSort: function(state) {
117231         var ds = this.up('tablepanel').store;
117232         ds.sort({
117233             property: this.getSortParam(),
117234             direction: state
117235         });
117236     },
117237
117238     /**
117239      * Returns the parameter to sort upon when sorting this header. By default this returns the dataIndex and will not
117240      * need to be overriden in most cases.
117241      * @return {String}
117242      */
117243     getSortParam: function() {
117244         return this.dataIndex;
117245     },
117246
117247     //setSortState: function(state, updateUI) {
117248     //setSortState: function(state, doSort) {
117249     setSortState: function(state, skipClear, initial) {
117250         var me = this,
117251             colSortClsPrefix = Ext.baseCSSPrefix + 'column-header-sort-',
117252             ascCls = colSortClsPrefix + 'ASC',
117253             descCls = colSortClsPrefix + 'DESC',
117254             nullCls = colSortClsPrefix + 'null',
117255             ownerHeaderCt = me.getOwnerHeaderCt(),
117256             oldSortState = me.sortState;
117257
117258         if (oldSortState !== state && me.getSortParam()) {
117259             me.addCls(colSortClsPrefix + state);
117260             // don't trigger a sort on the first time, we just want to update the UI
117261             if (state && !initial) {
117262                 me.doSort(state);
117263             }
117264             switch (state) {
117265                 case 'DESC':
117266                     me.removeCls([ascCls, nullCls]);
117267                     break;
117268                 case 'ASC':
117269                     me.removeCls([descCls, nullCls]);
117270                     break;
117271                 case null:
117272                     me.removeCls([ascCls, descCls]);
117273                     break;
117274             }
117275             if (ownerHeaderCt && !me.triStateSort && !skipClear) {
117276                 ownerHeaderCt.clearOtherSortStates(me);
117277             }
117278             me.sortState = state;
117279             ownerHeaderCt.fireEvent('sortchange', ownerHeaderCt, me, state);
117280         }
117281     },
117282
117283     hide: function() {
117284         var me = this,
117285             items,
117286             len, i,
117287             lb,
117288             newWidth = 0,
117289             ownerHeaderCt = me.getOwnerHeaderCt();
117290
117291         // Hiding means setting to zero width, so cache the width
117292         me.oldWidth = me.getWidth();
117293
117294         // Hiding a group header hides itself, and then informs the HeaderContainer about its sub headers (Suppressing header layout)
117295         if (me.isGroupHeader) {
117296             items = me.items.items;
117297             me.callParent(arguments);
117298             ownerHeaderCt.onHeaderHide(me);
117299             for (i = 0, len = items.length; i < len; i++) {
117300                 items[i].hidden = true;
117301                 ownerHeaderCt.onHeaderHide(items[i], true);
117302             }
117303             return;
117304         }
117305
117306         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
117307         lb = me.ownerCt.componentLayout.layoutBusy;
117308         me.ownerCt.componentLayout.layoutBusy = true;
117309         me.callParent(arguments);
117310         me.ownerCt.componentLayout.layoutBusy = lb;
117311
117312         // Notify owning HeaderContainer
117313         ownerHeaderCt.onHeaderHide(me);
117314
117315         if (me.ownerCt.isGroupHeader) {
117316             // If we've just hidden the last header in a group, then hide the group
117317             items = me.ownerCt.query('>:not([hidden])');
117318             if (!items.length) {
117319                 me.ownerCt.hide();
117320             }
117321             // Size the group down to accommodate fewer sub headers
117322             else {
117323                 for (i = 0, len = items.length; i < len; i++) {
117324                     newWidth += items[i].getWidth();
117325                 }
117326                 me.ownerCt.minWidth = newWidth;
117327                 me.ownerCt.setWidth(newWidth);
117328             }
117329         }
117330     },
117331
117332     show: function() {
117333         var me = this,
117334             ownerCt = me.ownerCt,
117335             ownerCtCompLayout = ownerCt.componentLayout,
117336             ownerCtCompLayoutBusy = ownerCtCompLayout.layoutBusy,
117337             ownerCtLayout = ownerCt.layout,
117338             ownerCtLayoutBusy = ownerCtLayout.layoutBusy,
117339             items,
117340             len, i,
117341             item,
117342             newWidth = 0;
117343
117344         // TODO: Work with Jamie to produce a scheme where we can show/hide/resize without triggering a layout cascade
117345
117346         // Suspend our owner's layouts (both component and container):
117347         ownerCtCompLayout.layoutBusy = ownerCtLayout.layoutBusy = true;
117348
117349         me.callParent(arguments);
117350
117351         ownerCtCompLayout.layoutBusy = ownerCtCompLayoutBusy;
117352         ownerCtLayout.layoutBusy = ownerCtLayoutBusy;
117353
117354         // If a sub header, ensure that the group header is visible
117355         if (me.isSubHeader) {
117356             if (!ownerCt.isVisible()) {
117357                 ownerCt.show();
117358             }
117359         }
117360
117361         // If we've just shown a group with all its sub headers hidden, then show all its sub headers
117362         if (me.isGroupHeader && !me.query(':not([hidden])').length) {
117363             items = me.query('>*');
117364             for (i = 0, len = items.length; i < len; i++) {
117365                 item = items[i];
117366                 item.preventLayout = true;
117367                 item.show();
117368                 newWidth += item.getWidth();
117369                 delete item.preventLayout;
117370             }
117371             me.setWidth(newWidth);
117372         }
117373
117374         // Resize the owning group to accommodate
117375         if (ownerCt.isGroupHeader && me.preventLayout !== true) {
117376             items = ownerCt.query('>:not([hidden])');
117377             for (i = 0, len = items.length; i < len; i++) {
117378                 newWidth += items[i].getWidth();
117379             }
117380             ownerCt.minWidth = newWidth;
117381             ownerCt.setWidth(newWidth);
117382         }
117383
117384         // Notify owning HeaderContainer
117385         ownerCt = me.getOwnerHeaderCt();
117386         if (ownerCt) {
117387             ownerCt.onHeaderShow(me, me.preventLayout);
117388         }
117389     },
117390
117391     getDesiredWidth: function() {
117392         var me = this;
117393         if (me.rendered && me.componentLayout && me.componentLayout.lastComponentSize) {
117394             // headers always have either a width or a flex
117395             // because HeaderContainer sets a defaults width
117396             // therefore we can ignore the natural width
117397             // we use the componentLayout's tracked width so that
117398             // we can calculate the desired width when rendered
117399             // but not visible because its being obscured by a layout
117400             return me.componentLayout.lastComponentSize.width;
117401         // Flexed but yet to be rendered this could be the case
117402         // where a HeaderContainer and Headers are simply used as data
117403         // structures and not rendered.
117404         }
117405         else if (me.flex) {
117406             // this is going to be wrong, the defaultWidth
117407             return me.width;
117408         }
117409         else {
117410             return me.width;
117411         }
117412     },
117413
117414     getCellSelector: function() {
117415         return '.' + Ext.baseCSSPrefix + 'grid-cell-' + this.getItemId();
117416     },
117417
117418     getCellInnerSelector: function() {
117419         return this.getCellSelector() + ' .' + Ext.baseCSSPrefix + 'grid-cell-inner';
117420     },
117421
117422     isOnLeftEdge: function(e) {
117423         return (e.getXY()[0] - this.el.getLeft() <= this.handleWidth);
117424     },
117425
117426     isOnRightEdge: function(e) {
117427         return (this.el.getRight() - e.getXY()[0] <= this.handleWidth);
117428     }
117429
117430     // intentionally omit getEditor and setEditor definitions bc we applyIf into columns
117431     // when the editing plugin is injected
117432
117433     /**
117434      * @method getEditor
117435      * Retrieves the editing field for editing associated with this header. Returns false if there is no field
117436      * associated with the Header the method will return false. If the field has not been instantiated it will be
117437      * created. Note: These methods only has an implementation if a Editing plugin has been enabled on the grid.
117438      * @param {Object} record The {@link Ext.data.Model Model} instance being edited.
117439      * @param {Object} defaultField An object representing a default field to be created
117440      * @return {Ext.form.field.Field} field
117441      */
117442     /**
117443      * @method setEditor
117444      * Sets the form field to be used for editing. Note: This method only has an implementation if an Editing plugin has
117445      * been enabled on the grid.
117446      * @param {Object} field An object representing a field to be created. If no xtype is specified a 'textfield' is
117447      * assumed.
117448      */
117449 });
117450
117451 /**
117452  * This is a utility class that can be passed into a {@link Ext.grid.column.Column} as a column config that provides
117453  * an automatic row numbering column.
117454  * 
117455  * Usage:
117456  *
117457  *     columns: [
117458  *         {xtype: 'rownumberer'},
117459  *         {text: "Company", flex: 1, sortable: true, dataIndex: 'company'},
117460  *         {text: "Price", width: 120, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
117461  *         {text: "Change", width: 120, sortable: true, dataIndex: 'change'},
117462  *         {text: "% Change", width: 120, sortable: true, dataIndex: 'pctChange'},
117463  *         {text: "Last Updated", width: 120, sortable: true, renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
117464  *     ]
117465  *
117466  */
117467 Ext.define('Ext.grid.RowNumberer', {
117468     extend: 'Ext.grid.column.Column',
117469     alias: 'widget.rownumberer',
117470
117471     /**
117472      * @cfg {String} text
117473      * Any valid text or HTML fragment to display in the header cell for the row number column.
117474      */
117475     text: "&#160",
117476
117477     /**
117478      * @cfg {Number} width
117479      * The default width in pixels of the row number column.
117480      */
117481     width: 23,
117482
117483     /**
117484      * @cfg {Boolean} sortable
117485      * True if the row number column is sortable.
117486      * @hide
117487      */
117488     sortable: false,
117489
117490     align: 'right',
117491
117492     constructor : function(config){
117493         this.callParent(arguments);
117494         if (this.rowspan) {
117495             this.renderer = Ext.Function.bind(this.renderer, this);
117496         }
117497     },
117498
117499     // private
117500     resizable: false,
117501     hideable: false,
117502     menuDisabled: true,
117503     dataIndex: '',
117504     cls: Ext.baseCSSPrefix + 'row-numberer',
117505     rowspan: undefined,
117506
117507     // private
117508     renderer: function(value, metaData, record, rowIdx, colIdx, store) {
117509         if (this.rowspan){
117510             metaData.cellAttr = 'rowspan="'+this.rowspan+'"';
117511         }
117512
117513         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
117514         return store.indexOfTotal(record) + 1;
117515     }
117516 });
117517
117518 /**
117519  * @class Ext.view.DropZone
117520  * @extends Ext.dd.DropZone
117521  * @private
117522  */
117523 Ext.define('Ext.view.DropZone', {
117524     extend: 'Ext.dd.DropZone',
117525
117526     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
117527     indicatorCls: 'x-grid-drop-indicator',
117528
117529     constructor: function(config) {
117530         var me = this;
117531         Ext.apply(me, config);
117532
117533         // Create a ddGroup unless one has been configured.
117534         // User configuration of ddGroups allows users to specify which
117535         // DD instances can interact with each other. Using one
117536         // based on the id of the View would isolate it and mean it can only
117537         // interact with a DragZone on the same View also using a generated ID.
117538         if (!me.ddGroup) {
117539             me.ddGroup = 'view-dd-zone-' + me.view.id;
117540         }
117541
117542         // The DropZone's encapsulating element is the View's main element. It must be this because drop gestures
117543         // may require scrolling on hover near a scrolling boundary. In Ext 4.x two DD instances may not use the
117544         // same element, so a DragZone on this same View must use the View's parent element as its element.
117545         me.callParent([me.view.el]);
117546     },
117547
117548 //  Fire an event through the client DataView. Lock this DropZone during the event processing so that
117549 //  its data does not become corrupted by processing mouse events.
117550     fireViewEvent: function() {
117551         var me = this,
117552             result;
117553
117554         me.lock();
117555         result = me.view.fireEvent.apply(me.view, arguments);
117556         me.unlock();
117557         return result;
117558     },
117559
117560     getTargetFromEvent : function(e) {
117561         var node = e.getTarget(this.view.getItemSelector()),
117562             mouseY, nodeList, testNode, i, len, box;
117563
117564 //      Not over a row node: The content may be narrower than the View's encapsulating element, so return the closest.
117565 //      If we fall through because the mouse is below the nodes (or there are no nodes), we'll get an onContainerOver call.
117566         if (!node) {
117567             mouseY = e.getPageY();
117568             for (i = 0, nodeList = this.view.getNodes(), len = nodeList.length; i < len; i++) {
117569                 testNode = nodeList[i];
117570                 box = Ext.fly(testNode).getBox();
117571                 if (mouseY <= box.bottom) {
117572                     return testNode;
117573                 }
117574             }
117575         }
117576         return node;
117577     },
117578
117579     getIndicator: function() {
117580         var me = this;
117581
117582         if (!me.indicator) {
117583             me.indicator = Ext.createWidget('component', {
117584                 html: me.indicatorHtml,
117585                 cls: me.indicatorCls,
117586                 ownerCt: me.view,
117587                 floating: true,
117588                 shadow: false
117589             });
117590         }
117591         return me.indicator;
117592     },
117593
117594     getPosition: function(e, node) {
117595         var y      = e.getXY()[1],
117596             region = Ext.fly(node).getRegion(),
117597             pos;
117598
117599         if ((region.bottom - y) >= (region.bottom - region.top) / 2) {
117600             pos = "before";
117601         } else {
117602             pos = "after";
117603         }
117604         return pos;
117605     },
117606
117607     /**
117608      * @private Determines whether the record at the specified offset from the passed record
117609      * is in the drag payload.
117610      * @param records
117611      * @param record
117612      * @param offset
117613      * @returns {Boolean} True if the targeted record is in the drag payload
117614      */
117615     containsRecordAtOffset: function(records, record, offset) {
117616         if (!record) {
117617             return false;
117618         }
117619         var view = this.view,
117620             recordIndex = view.indexOf(record),
117621             nodeBefore = view.getNode(recordIndex + offset),
117622             recordBefore = nodeBefore ? view.getRecord(nodeBefore) : null;
117623
117624         return recordBefore && Ext.Array.contains(records, recordBefore);
117625     },
117626
117627     positionIndicator: function(node, data, e) {
117628         var me = this,
117629             view = me.view,
117630             pos = me.getPosition(e, node),
117631             overRecord = view.getRecord(node),
117632             draggingRecords = data.records,
117633             indicator, indicatorY;
117634
117635         if (!Ext.Array.contains(draggingRecords, overRecord) && (
117636             pos == 'before' && !me.containsRecordAtOffset(draggingRecords, overRecord, -1) ||
117637             pos == 'after' && !me.containsRecordAtOffset(draggingRecords, overRecord, 1)
117638         )) {
117639             me.valid = true;
117640
117641             if (me.overRecord != overRecord || me.currentPosition != pos) {
117642
117643                 indicatorY = Ext.fly(node).getY() - view.el.getY() - 1;
117644                 if (pos == 'after') {
117645                     indicatorY += Ext.fly(node).getHeight();
117646                 }
117647                 me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, indicatorY);
117648
117649                 // Cache the overRecord and the 'before' or 'after' indicator.
117650                 me.overRecord = overRecord;
117651                 me.currentPosition = pos;
117652             }
117653         } else {
117654             me.invalidateDrop();
117655         }
117656     },
117657
117658     invalidateDrop: function() {
117659         if (this.valid) {
117660             this.valid = false;
117661             this.getIndicator().hide();
117662         }
117663     },
117664
117665     // The mouse is over a View node
117666     onNodeOver: function(node, dragZone, e, data) {
117667         var me = this;
117668
117669         if (!Ext.Array.contains(data.records, me.view.getRecord(node))) {
117670             me.positionIndicator(node, data, e);
117671         }
117672         return me.valid ? me.dropAllowed : me.dropNotAllowed;
117673     },
117674
117675     // Moved out of the DropZone without dropping.
117676     // Remove drop position indicator
117677     notifyOut: function(node, dragZone, e, data) {
117678         var me = this;
117679
117680         me.callParent(arguments);
117681         delete me.overRecord;
117682         delete me.currentPosition;
117683         if (me.indicator) {
117684             me.indicator.hide();
117685         }
117686     },
117687
117688     // The mouse is past the end of all nodes (or there are no nodes)
117689     onContainerOver : function(dd, e, data) {
117690         var me = this,
117691             view = me.view,
117692             count = view.store.getCount();
117693
117694         // There are records, so position after the last one
117695         if (count) {
117696             me.positionIndicator(view.getNode(count - 1), data, e);
117697         }
117698
117699         // No records, position the indicator at the top
117700         else {
117701             delete me.overRecord;
117702             delete me.currentPosition;
117703             me.getIndicator().setWidth(Ext.fly(view.el).getWidth()).showAt(0, 0);
117704             me.valid = true;
117705         }
117706         return me.dropAllowed;
117707     },
117708
117709     onContainerDrop : function(dd, e, data) {
117710         return this.onNodeDrop(dd, null, e, data);
117711     },
117712
117713     onNodeDrop: function(node, dragZone, e, data) {
117714         var me = this,
117715             dropped = false,
117716
117717             // Create a closure to perform the operation which the event handler may use.
117718             // Users may now return <code>false</code> from the beforedrop handler, and perform any kind
117719             // of asynchronous processing such as an Ext.Msg.confirm, or an Ajax request,
117720             // and complete the drop gesture at some point in the future by calling this function.
117721             processDrop = function () {
117722                 me.invalidateDrop();
117723                 me.handleNodeDrop(data, me.overRecord, me.currentPosition);
117724                 dropped = true;
117725                 me.fireViewEvent('drop', node, data, me.overRecord, me.currentPosition);
117726             },
117727             performOperation = false;
117728
117729         if (me.valid) {
117730             performOperation = me.fireViewEvent('beforedrop', node, data, me.overRecord, me.currentPosition, processDrop);
117731             if (performOperation !== false) {
117732                 // If the processDrop function was called in the event handler, do not do it again.
117733                 if (!dropped) {
117734                     processDrop();
117735                 }
117736             }
117737         }
117738         return performOperation;
117739     },
117740     
117741     destroy: function(){
117742         Ext.destroy(this.indicator);
117743         delete this.indicator;
117744         this.callParent();
117745     }
117746 });
117747
117748 Ext.define('Ext.grid.ViewDropZone', {
117749     extend: 'Ext.view.DropZone',
117750
117751     indicatorHtml: '<div class="x-grid-drop-indicator-left"></div><div class="x-grid-drop-indicator-right"></div>',
117752     indicatorCls: 'x-grid-drop-indicator',
117753
117754     handleNodeDrop : function(data, record, position) {
117755         var view = this.view,
117756             store = view.getStore(),
117757             index, records, i, len;
117758
117759         // If the copy flag is set, create a copy of the Models with the same IDs
117760         if (data.copy) {
117761             records = data.records;
117762             data.records = [];
117763             for (i = 0, len = records.length; i < len; i++) {
117764                 data.records.push(records[i].copy(records[i].getId()));
117765             }
117766         } else {
117767             /*
117768              * Remove from the source store. We do this regardless of whether the store
117769              * is the same bacsue the store currently doesn't handle moving records
117770              * within the store. In the future it should be possible to do this.
117771              * Here was pass the isMove parameter if we're moving to the same view.
117772              */
117773             data.view.store.remove(data.records, data.view === view);
117774         }
117775
117776         index = store.indexOf(record);
117777
117778         // 'after', or undefined (meaning a drop at index -1 on an empty View)...
117779         if (position !== 'before') {
117780             index++;
117781         }
117782         store.insert(index, data.records);
117783         view.getSelectionModel().select(data.records);
117784     }
117785 });
117786 /**
117787  * A Grid header type which renders an icon, or a series of icons in a grid cell, and offers a scoped click
117788  * handler for each icon.
117789  *
117790  *     @example
117791  *     Ext.create('Ext.data.Store', {
117792  *         storeId:'employeeStore',
117793  *         fields:['firstname', 'lastname', 'senority', 'dep', 'hired'],
117794  *         data:[
117795  *             {firstname:"Michael", lastname:"Scott"},
117796  *             {firstname:"Dwight", lastname:"Schrute"},
117797  *             {firstname:"Jim", lastname:"Halpert"},
117798  *             {firstname:"Kevin", lastname:"Malone"},
117799  *             {firstname:"Angela", lastname:"Martin"}
117800  *         ]
117801  *     });
117802  *
117803  *     Ext.create('Ext.grid.Panel', {
117804  *         title: 'Action Column Demo',
117805  *         store: Ext.data.StoreManager.lookup('employeeStore'),
117806  *         columns: [
117807  *             {text: 'First Name',  dataIndex:'firstname'},
117808  *             {text: 'Last Name',  dataIndex:'lastname'},
117809  *             {
117810  *                 xtype:'actioncolumn',
117811  *                 width:50,
117812  *                 items: [{
117813  *                     icon: 'extjs/examples/shared/icons/fam/cog_edit.png',  // Use a URL in the icon config
117814  *                     tooltip: 'Edit',
117815  *                     handler: function(grid, rowIndex, colIndex) {
117816  *                         var rec = grid.getStore().getAt(rowIndex);
117817  *                         alert("Edit " + rec.get('firstname'));
117818  *                     }
117819  *                 },{
117820  *                     icon: 'extjs/examples/restful/images/delete.png',
117821  *                     tooltip: 'Delete',
117822  *                     handler: function(grid, rowIndex, colIndex) {
117823  *                         var rec = grid.getStore().getAt(rowIndex);
117824  *                         alert("Terminate " + rec.get('firstname'));
117825  *                     }
117826  *                 }]
117827  *             }
117828  *         ],
117829  *         width: 250,
117830  *         renderTo: Ext.getBody()
117831  *     });
117832  *
117833  * The action column can be at any index in the columns array, and a grid can have any number of
117834  * action columns.
117835  */
117836 Ext.define('Ext.grid.column.Action', {
117837     extend: 'Ext.grid.column.Column',
117838     alias: ['widget.actioncolumn'],
117839     alternateClassName: 'Ext.grid.ActionColumn',
117840
117841     /**
117842      * @cfg {String} icon
117843      * The URL of an image to display as the clickable element in the column. Defaults to
117844      * `{@link Ext#BLANK_IMAGE_URL Ext.BLANK_IMAGE_URL}`.
117845      */
117846     /**
117847      * @cfg {String} iconCls
117848      * A CSS class to apply to the icon image. To determine the class dynamically, configure the Column with
117849      * a `{@link #getClass}` function.
117850      */
117851     /**
117852      * @cfg {Function} handler
117853      * A function called when the icon is clicked.
117854      * @cfg {Ext.view.Table} handler.view The owning TableView.
117855      * @cfg {Number} handler.rowIndex The row index clicked on.
117856      * @cfg {Number} handler.colIndex The column index clicked on.
117857      * @cfg {Object} handler.item The clicked item (or this Column if multiple {@link #items} were not configured).
117858      * @cfg {Event} handler.e The click event.
117859      */
117860     /**
117861      * @cfg {Object} scope
117862      * The scope (**this** reference) in which the `{@link #handler}` and `{@link #getClass}` fuctions are executed.
117863      * Defaults to this Column.
117864      */
117865     /**
117866      * @cfg {String} tooltip
117867      * A tooltip message to be displayed on hover. {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must
117868      * have been initialized.
117869      */
117870     /* @cfg {Boolean} disabled
117871      * If true, the action will not respond to click events, and will be displayed semi-opaque.
117872      */
117873     /**
117874      * @cfg {Boolean} [stopSelection=true]
117875      * Prevent grid _row_ selection upon mousedown.
117876      */
117877     /**
117878      * @cfg {Function} getClass
117879      * A function which returns the CSS class to apply to the icon image.
117880      *
117881      * @cfg {Object} getClass.v The value of the column's configured field (if any).
117882      *
117883      * @cfg {Object} getClass.metadata An object in which you may set the following attributes:
117884      * @cfg {String} getClass.metadata.css A CSS class name to add to the cell's TD element.
117885      * @cfg {String} getClass.metadata.attr An HTML attribute definition string to apply to the data container
117886      * element *within* the table cell (e.g. 'style="color:red;"').
117887      *
117888      * @cfg {Ext.data.Model} getClass.r The Record providing the data.
117889      *
117890      * @cfg {Number} getClass.rowIndex The row index..
117891      *
117892      * @cfg {Number} getClass.colIndex The column index.
117893      *
117894      * @cfg {Ext.data.Store} getClass.store The Store which is providing the data Model.
117895      */
117896     /**
117897      * @cfg {Object[]} items
117898      * An Array which may contain multiple icon definitions, each element of which may contain:
117899      *
117900      * @cfg {String} items.icon The url of an image to display as the clickable element in the column.
117901      *
117902      * @cfg {String} items.iconCls A CSS class to apply to the icon image. To determine the class dynamically,
117903      * configure the item with a `getClass` function.
117904      *
117905      * @cfg {Function} items.getClass A function which returns the CSS class to apply to the icon image.
117906      * @cfg {Object} items.getClass.v The value of the column's configured field (if any).
117907      * @cfg {Object} items.getClass.metadata An object in which you may set the following attributes:
117908      * @cfg {String} items.getClass.metadata.css A CSS class name to add to the cell's TD element.
117909      * @cfg {String} items.getClass.metadata.attr An HTML attribute definition string to apply to the data
117910      * container element _within_ the table cell (e.g. 'style="color:red;"').
117911      * @cfg {Ext.data.Model} items.getClass.r The Record providing the data.
117912      * @cfg {Number} items.getClass.rowIndex The row index..
117913      * @cfg {Number} items.getClass.colIndex The column index.
117914      * @cfg {Ext.data.Store} items.getClass.store The Store which is providing the data Model.
117915      *
117916      * @cfg {Function} items.handler A function called when the icon is clicked.
117917      *
117918      * @cfg {Object} items.scope The scope (`this` reference) in which the `handler` and `getClass` functions
117919      * are executed. Fallback defaults are this Column's configured scope, then this Column.
117920      *
117921      * @cfg {String} items.tooltip A tooltip message to be displayed on hover.
117922      * @cfg {Boolean} items.disabled If true, the action will not respond to click events, and will be displayed semi-opaque.
117923      * {@link Ext.tip.QuickTipManager#init Ext.tip.QuickTipManager} must have been initialized.
117924      */
117925     /**
117926      * @property {Array} items
117927      * An array of action items copied from the configured {@link #cfg-items items} configuration. Each will have
117928      * an `enable` and `disable` method added which will enable and disable the associated action, and
117929      * update the displayed icon accordingly.
117930      */
117931     header: '&#160;',
117932
117933     actionIdRe: new RegExp(Ext.baseCSSPrefix + 'action-col-(\\d+)'),
117934
117935     /**
117936      * @cfg {String} altText
117937      * The alt text to use for the image element.
117938      */
117939     altText: '',
117940
117941     sortable: false,
117942
117943     constructor: function(config) {
117944         var me = this,
117945             cfg = Ext.apply({}, config),
117946             items = cfg.items || [me],
117947             l = items.length,
117948             i,
117949             item;
117950
117951         // This is a Container. Delete the items config to be reinstated after construction.
117952         delete cfg.items;
117953         me.callParent([cfg]);
117954
117955         // Items is an array property of ActionColumns
117956         me.items = items;
117957
117958 //      Renderer closure iterates through items creating an <img> element for each and tagging with an identifying
117959 //      class name x-action-col-{n}
117960         me.renderer = function(v, meta) {
117961 //          Allow a configured renderer to create initial value (And set the other values in the "metadata" argument!)
117962             v = Ext.isFunction(cfg.renderer) ? cfg.renderer.apply(this, arguments)||'' : '';
117963
117964             meta.tdCls += ' ' + Ext.baseCSSPrefix + 'action-col-cell';
117965             for (i = 0; i < l; i++) {
117966                 item = items[i];
117967                 item.disable = Ext.Function.bind(me.disableAction, me, [i]);
117968                 item.enable = Ext.Function.bind(me.enableAction, me, [i]);
117969                 v += '<img alt="' + (item.altText || me.altText) + '" src="' + (item.icon || Ext.BLANK_IMAGE_URL) +
117970                     '" class="' + Ext.baseCSSPrefix + 'action-col-icon ' + Ext.baseCSSPrefix + 'action-col-' + String(i) + ' ' + (item.disabled ? Ext.baseCSSPrefix + 'item-disabled' : ' ') + (item.iconCls || '') +
117971                     ' ' + (Ext.isFunction(item.getClass) ? item.getClass.apply(item.scope||me.scope||me, arguments) : (me.iconCls || '')) + '"' +
117972                     ((item.tooltip) ? ' data-qtip="' + item.tooltip + '"' : '') + ' />';
117973             }
117974             return v;
117975         };
117976     },
117977
117978     /**
117979      * Enables this ActionColumn's action at the specified index.
117980      */
117981     enableAction: function(index) {
117982         var me = this;
117983
117984         if (!index) {
117985             index = 0;
117986         } else if (!Ext.isNumber(index)) {
117987             index = Ext.Array.indexOf(me.items, index);
117988         }
117989         me.items[index].disabled = false;
117990         me.up('tablepanel').el.select('.' + Ext.baseCSSPrefix + 'action-col-' + index).removeCls(me.disabledCls);
117991     },
117992
117993     /**
117994      * Disables this ActionColumn's action at the specified index.
117995      */
117996     disableAction: function(index) {
117997         var me = this;
117998
117999         if (!index) {
118000             index = 0;
118001         } else if (!Ext.isNumber(index)) {
118002             index = Ext.Array.indexOf(me.items, index);
118003         }
118004         me.items[index].disabled = true;
118005         me.up('tablepanel').el.select('.' + Ext.baseCSSPrefix + 'action-col-' + index).addCls(me.disabledCls);
118006     },
118007
118008     destroy: function() {
118009         delete this.items;
118010         delete this.renderer;
118011         return this.callParent(arguments);
118012     },
118013
118014     /**
118015      * @private
118016      * Process and refire events routed from the GridView's processEvent method.
118017      * Also fires any configured click handlers. By default, cancels the mousedown event to prevent selection.
118018      * Returns the event handler's status to allow canceling of GridView's bubbling process.
118019      */
118020     processEvent : function(type, view, cell, recordIndex, cellIndex, e){
118021         var me = this,
118022             match = e.getTarget().className.match(me.actionIdRe),
118023             item, fn;
118024             
118025         if (match) {
118026             item = me.items[parseInt(match[1], 10)];
118027             if (item) {
118028                 if (type == 'click') {
118029                     fn = item.handler || me.handler;
118030                     if (fn && !item.disabled) {
118031                         fn.call(item.scope || me.scope || me, view, recordIndex, cellIndex, item, e);
118032                     }
118033                 } else if (type == 'mousedown' && item.stopSelection !== false) {
118034                     return false;
118035                 }
118036             }
118037         }
118038         return me.callParent(arguments);
118039     },
118040
118041     cascade: function(fn, scope) {
118042         fn.call(scope||this, this);
118043     },
118044
118045     // Private override because this cannot function as a Container, and it has an items property which is an Array, NOT a MixedCollection.
118046     getRefItems: function() {
118047         return [];
118048     }
118049 });
118050 /**
118051  * A Column definition class which renders boolean data fields.  See the {@link Ext.grid.column.Column#xtype xtype}
118052  * config option of {@link Ext.grid.column.Column} for more details.
118053  *
118054  *     @example
118055  *     Ext.create('Ext.data.Store', {
118056  *        storeId:'sampleStore',
118057  *        fields:[
118058  *            {name: 'framework', type: 'string'},
118059  *            {name: 'rocks', type: 'boolean'}
118060  *        ],
118061  *        data:{'items':[
118062  *            { 'framework': "Ext JS 4",     'rocks': true  },
118063  *            { 'framework': "Sencha Touch", 'rocks': true  },
118064  *            { 'framework': "Ext GWT",      'rocks': true  }, 
118065  *            { 'framework': "Other Guys",   'rocks': false } 
118066  *        ]},
118067  *        proxy: {
118068  *            type: 'memory',
118069  *            reader: {
118070  *                type: 'json',
118071  *                root: 'items'
118072  *            }
118073  *        }
118074  *     });
118075  *     
118076  *     Ext.create('Ext.grid.Panel', {
118077  *         title: 'Boolean Column Demo',
118078  *         store: Ext.data.StoreManager.lookup('sampleStore'),
118079  *         columns: [
118080  *             { text: 'Framework',  dataIndex: 'framework', flex: 1 },
118081  *             {
118082  *                 xtype: 'booleancolumn', 
118083  *                 text: 'Rocks',
118084  *                 trueText: 'Yes',
118085  *                 falseText: 'No', 
118086  *                 dataIndex: 'rocks'
118087  *             }
118088  *         ],
118089  *         height: 200,
118090  *         width: 400,
118091  *         renderTo: Ext.getBody()
118092  *     });
118093  */
118094 Ext.define('Ext.grid.column.Boolean', {
118095     extend: 'Ext.grid.column.Column',
118096     alias: ['widget.booleancolumn'],
118097     alternateClassName: 'Ext.grid.BooleanColumn',
118098
118099     /**
118100      * @cfg {String} trueText
118101      * The string returned by the renderer when the column value is not falsey.
118102      */
118103     trueText: 'true',
118104
118105     /**
118106      * @cfg {String} falseText
118107      * The string returned by the renderer when the column value is falsey (but not undefined).
118108      */
118109     falseText: 'false',
118110
118111     /**
118112      * @cfg {String} undefinedText
118113      * The string returned by the renderer when the column value is undefined.
118114      */
118115     undefinedText: '&#160;',
118116
118117     constructor: function(cfg){
118118         this.callParent(arguments);
118119         var trueText      = this.trueText,
118120             falseText     = this.falseText,
118121             undefinedText = this.undefinedText;
118122
118123         this.renderer = function(value){
118124             if(value === undefined){
118125                 return undefinedText;
118126             }
118127             if(!value || value === 'false'){
118128                 return falseText;
118129             }
118130             return trueText;
118131         };
118132     }
118133 });
118134 /**
118135  * A Column definition class which renders a passed date according to the default locale, or a configured
118136  * {@link #format}.
118137  *
118138  *     @example
118139  *     Ext.create('Ext.data.Store', {
118140  *         storeId:'sampleStore',
118141  *         fields:[
118142  *             { name: 'symbol', type: 'string' },
118143  *             { name: 'date',   type: 'date' },
118144  *             { name: 'change', type: 'number' },
118145  *             { name: 'volume', type: 'number' },
118146  *             { name: 'topday', type: 'date' }                        
118147  *         ],
118148  *         data:[
118149  *             { symbol: "msft",   date: '2011/04/22', change: 2.43, volume: 61606325, topday: '04/01/2010' },
118150  *             { symbol: "goog",   date: '2011/04/22', change: 0.81, volume: 3053782,  topday: '04/11/2010' },
118151  *             { symbol: "apple",  date: '2011/04/22', change: 1.35, volume: 24484858, topday: '04/28/2010' },            
118152  *             { symbol: "sencha", date: '2011/04/22', change: 8.85, volume: 5556351,  topday: '04/22/2010' }            
118153  *         ]
118154  *     });
118155  *     
118156  *     Ext.create('Ext.grid.Panel', {
118157  *         title: 'Date Column Demo',
118158  *         store: Ext.data.StoreManager.lookup('sampleStore'),
118159  *         columns: [
118160  *             { text: 'Symbol',   dataIndex: 'symbol', flex: 1 },
118161  *             { text: 'Date',     dataIndex: 'date',   xtype: 'datecolumn',   format:'Y-m-d' },
118162  *             { text: 'Change',   dataIndex: 'change', xtype: 'numbercolumn', format:'0.00' },
118163  *             { text: 'Volume',   dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000' },
118164  *             { text: 'Top Day',  dataIndex: 'topday', xtype: 'datecolumn',   format:'l' }            
118165  *         ],
118166  *         height: 200,
118167  *         width: 450,
118168  *         renderTo: Ext.getBody()
118169  *     });
118170  */
118171 Ext.define('Ext.grid.column.Date', {
118172     extend: 'Ext.grid.column.Column',
118173     alias: ['widget.datecolumn'],
118174     requires: ['Ext.Date'],
118175     alternateClassName: 'Ext.grid.DateColumn',
118176
118177     /**
118178      * @cfg {String} format
118179      * A formatting string as used by {@link Ext.Date#format} to format a Date for this Column.
118180      * This defaults to the default date from {@link Ext.Date#defaultFormat} which itself my be overridden
118181      * in a locale file.
118182      */
118183
118184     initComponent: function(){
118185         var me = this;
118186         
118187         me.callParent(arguments);
118188         if (!me.format) {
118189             me.format = Ext.Date.defaultFormat;
118190         }
118191         me.renderer = Ext.util.Format.dateRenderer(me.format);
118192     }
118193 });
118194 /**
118195  * A Column definition class which renders a numeric data field according to a {@link #format} string.
118196  *
118197  *     @example
118198  *     Ext.create('Ext.data.Store', {
118199  *        storeId:'sampleStore',
118200  *        fields:[
118201  *            { name: 'symbol', type: 'string' },
118202  *            { name: 'price',  type: 'number' },
118203  *            { name: 'change', type: 'number' },
118204  *            { name: 'volume', type: 'number' },            
118205  *        ],
118206  *        data:[
118207  *            { symbol: "msft",   price: 25.76,  change: 2.43, volume: 61606325 },
118208  *            { symbol: "goog",   price: 525.73, change: 0.81, volume: 3053782  },
118209  *            { symbol: "apple",  price: 342.41, change: 1.35, volume: 24484858 },            
118210  *            { symbol: "sencha", price: 142.08, change: 8.85, volume: 5556351  }            
118211  *        ]
118212  *     });
118213  *     
118214  *     Ext.create('Ext.grid.Panel', {
118215  *         title: 'Number Column Demo',
118216  *         store: Ext.data.StoreManager.lookup('sampleStore'),
118217  *         columns: [
118218  *             { text: 'Symbol',         dataIndex: 'symbol', flex: 1 },
118219  *             { text: 'Current Price',  dataIndex: 'price',  renderer: Ext.util.Format.usMoney },
118220  *             { text: 'Change',         dataIndex: 'change', xtype: 'numbercolumn', format:'0.00' },
118221  *             { text: 'Volume',         dataIndex: 'volume', xtype: 'numbercolumn', format:'0,000' }
118222  *         ],
118223  *         height: 200,
118224  *         width: 400,
118225  *         renderTo: Ext.getBody()
118226  *     });
118227  */
118228 Ext.define('Ext.grid.column.Number', {
118229     extend: 'Ext.grid.column.Column',
118230     alias: ['widget.numbercolumn'],
118231     requires: ['Ext.util.Format'],
118232     alternateClassName: 'Ext.grid.NumberColumn',
118233
118234     /**
118235      * @cfg {String} format
118236      * A formatting string as used by {@link Ext.util.Format#number} to format a numeric value for this Column.
118237      */
118238     format : '0,000.00',
118239
118240     constructor: function(cfg) {
118241         this.callParent(arguments);
118242         this.renderer = Ext.util.Format.numberRenderer(this.format);
118243     }
118244 });
118245 /**
118246  * A Column definition class which renders a value by processing a {@link Ext.data.Model Model}'s
118247  * {@link Ext.data.Model#persistenceProperty data} using a {@link #tpl configured}
118248  * {@link Ext.XTemplate XTemplate}.
118249  * 
118250  *     @example
118251  *     Ext.create('Ext.data.Store', {
118252  *         storeId:'employeeStore',
118253  *         fields:['firstname', 'lastname', 'senority', 'department'],
118254  *         groupField: 'department',
118255  *         data:[
118256  *             { firstname: "Michael", lastname: "Scott",   senority: 7, department: "Manangement" },
118257  *             { firstname: "Dwight",  lastname: "Schrute", senority: 2, department: "Sales" },
118258  *             { firstname: "Jim",     lastname: "Halpert", senority: 3, department: "Sales" },
118259  *             { firstname: "Kevin",   lastname: "Malone",  senority: 4, department: "Accounting" },
118260  *             { firstname: "Angela",  lastname: "Martin",  senority: 5, department: "Accounting" }                        
118261  *         ]
118262  *     });
118263  *     
118264  *     Ext.create('Ext.grid.Panel', {
118265  *         title: 'Column Template Demo',
118266  *         store: Ext.data.StoreManager.lookup('employeeStore'),
118267  *         columns: [
118268  *             { text: 'Full Name',       xtype: 'templatecolumn', tpl: '{firstname} {lastname}', flex:1 },
118269  *             { text: 'Deparment (Yrs)', xtype: 'templatecolumn', tpl: '{department} ({senority})' }
118270  *         ],
118271  *         height: 200,
118272  *         width: 300,
118273  *         renderTo: Ext.getBody()
118274  *     });
118275  */
118276 Ext.define('Ext.grid.column.Template', {
118277     extend: 'Ext.grid.column.Column',
118278     alias: ['widget.templatecolumn'],
118279     requires: ['Ext.XTemplate'],
118280     alternateClassName: 'Ext.grid.TemplateColumn',
118281
118282     /**
118283      * @cfg {String/Ext.XTemplate} tpl
118284      * An {@link Ext.XTemplate XTemplate}, or an XTemplate *definition string* to use to process a
118285      * {@link Ext.data.Model Model}'s {@link Ext.data.Model#persistenceProperty data} to produce a
118286      * column's rendered value.
118287      */
118288
118289     constructor: function(cfg){
118290         var me = this,
118291             tpl;
118292             
118293         me.callParent(arguments);
118294         tpl = me.tpl = (!Ext.isPrimitive(me.tpl) && me.tpl.compile) ? me.tpl : Ext.create('Ext.XTemplate', me.tpl);
118295
118296         me.renderer = function(value, p, record) {
118297             var data = Ext.apply({}, record.data, record.getAssociatedData());
118298             return tpl.apply(data);
118299         };
118300     }
118301 });
118302
118303 /**
118304  * @class Ext.grid.feature.Feature
118305  * @extends Ext.util.Observable
118306  * 
118307  * A feature is a type of plugin that is specific to the {@link Ext.grid.Panel}. It provides several
118308  * hooks that allows the developer to inject additional functionality at certain points throughout the 
118309  * grid creation cycle. This class provides the base template methods that are available to the developer,
118310  * it should be extended.
118311  * 
118312  * There are several built in features that extend this class, for example:
118313  *
118314  *  - {@link Ext.grid.feature.Grouping} - Shows grid rows in groups as specified by the {@link Ext.data.Store}
118315  *  - {@link Ext.grid.feature.RowBody} - Adds a body section for each grid row that can contain markup.
118316  *  - {@link Ext.grid.feature.Summary} - Adds a summary row at the bottom of the grid with aggregate totals for a column.
118317  * 
118318  * ## Using Features
118319  * A feature is added to the grid by specifying it an array of features in the configuration:
118320  * 
118321  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping');
118322  *     Ext.create('Ext.grid.Panel', {
118323  *         // other options
118324  *         features: [groupingFeature]
118325  *     });
118326  * 
118327  * @abstract
118328  */
118329 Ext.define('Ext.grid.feature.Feature', {
118330     extend: 'Ext.util.Observable',
118331     alias: 'feature.feature',
118332     
118333     isFeature: true,
118334     disabled: false,
118335     
118336     /**
118337      * @property {Boolean}
118338      * Most features will expose additional events, some may not and will
118339      * need to change this to false.
118340      */
118341     hasFeatureEvent: true,
118342     
118343     /**
118344      * @property {String}
118345      * Prefix to use when firing events on the view.
118346      * For example a prefix of group would expose "groupclick", "groupcontextmenu", "groupdblclick".
118347      */
118348     eventPrefix: null,
118349     
118350     /**
118351      * @property {String}
118352      * Selector used to determine when to fire the event with the eventPrefix.
118353      */
118354     eventSelector: null,
118355     
118356     /**
118357      * @property {Ext.view.Table}
118358      * Reference to the TableView.
118359      */
118360     view: null,
118361     
118362     /**
118363      * @property {Ext.grid.Panel}
118364      * Reference to the grid panel
118365      */
118366     grid: null,
118367     
118368     /**
118369      * Most features will not modify the data returned to the view.
118370      * This is limited to one feature that manipulates the data per grid view.
118371      */
118372     collectData: false,
118373         
118374     getFeatureTpl: function() {
118375         return '';
118376     },
118377     
118378     /**
118379      * Abstract method to be overriden when a feature should add additional
118380      * arguments to its event signature. By default the event will fire:
118381      * - view - The underlying Ext.view.Table
118382      * - featureTarget - The matched element by the defined {@link #eventSelector}
118383      *
118384      * The method must also return the eventName as the first index of the array
118385      * to be passed to fireEvent.
118386      * @template
118387      */
118388     getFireEventArgs: function(eventName, view, featureTarget, e) {
118389         return [eventName, view, featureTarget, e];
118390     },
118391     
118392     /**
118393      * Approriate place to attach events to the view, selectionmodel, headerCt, etc
118394      * @template
118395      */
118396     attachEvents: function() {
118397         
118398     },
118399     
118400     getFragmentTpl: function() {
118401         return;
118402     },
118403     
118404     /**
118405      * Allows a feature to mutate the metaRowTpl.
118406      * The array received as a single argument can be manipulated to add things
118407      * on the end/begining of a particular row.
118408      * @template
118409      */
118410     mutateMetaRowTpl: function(metaRowTplArray) {
118411         
118412     },
118413     
118414     /**
118415      * Allows a feature to inject member methods into the metaRowTpl. This is
118416      * important for embedding functionality which will become part of the proper
118417      * row tpl.
118418      * @template
118419      */
118420     getMetaRowTplFragments: function() {
118421         return {};
118422     },
118423
118424     getTableFragments: function() {
118425         return {};
118426     },
118427     
118428     /**
118429      * Provide additional data to the prepareData call within the grid view.
118430      * @param {Object} data The data for this particular record.
118431      * @param {Number} idx The row index for this record.
118432      * @param {Ext.data.Model} record The record instance
118433      * @param {Object} orig The original result from the prepareData call to massage.
118434      * @template
118435      */
118436     getAdditionalData: function(data, idx, record, orig) {
118437         return {};
118438     },
118439     
118440     /**
118441      * Enable a feature
118442      */
118443     enable: function() {
118444         this.disabled = false;
118445     },
118446     
118447     /**
118448      * Disable a feature
118449      */
118450     disable: function() {
118451         this.disabled = true;
118452     }
118453     
118454 });
118455 /**
118456  * @class Ext.grid.feature.AbstractSummary
118457  * @extends Ext.grid.feature.Feature
118458  * A small abstract class that contains the shared behaviour for any summary
118459  * calculations to be used in the grid.
118460  */
118461 Ext.define('Ext.grid.feature.AbstractSummary', {
118462     
118463     /* Begin Definitions */
118464    
118465     extend: 'Ext.grid.feature.Feature',
118466     
118467     alias: 'feature.abstractsummary',
118468    
118469     /* End Definitions */
118470    
118471    /**
118472     * @cfg {Boolean} showSummaryRow True to show the summary row. Defaults to <tt>true</tt>.
118473     */
118474     showSummaryRow: true,
118475     
118476     // @private
118477     nestedIdRe: /\{\{id\}([\w\-]*)\}/g,
118478     
118479     /**
118480      * Toggle whether or not to show the summary row.
118481      * @param {Boolean} visible True to show the summary row
118482      */
118483     toggleSummaryRow: function(visible){
118484         this.showSummaryRow = !!visible;
118485     },
118486     
118487     /**
118488      * Gets any fragments to be used in the tpl
118489      * @private
118490      * @return {Object} The fragments
118491      */
118492     getSummaryFragments: function(){
118493         var fragments = {};
118494         if (this.showSummaryRow) {
118495             Ext.apply(fragments, {
118496                 printSummaryRow: Ext.bind(this.printSummaryRow, this)
118497             });
118498         }
118499         return fragments;
118500     },
118501     
118502     /**
118503      * Prints a summary row
118504      * @private
118505      * @param {Object} index The index in the template
118506      * @return {String} The value of the summary row
118507      */
118508     printSummaryRow: function(index){
118509         var inner = this.view.getTableChunker().metaRowTpl.join(''),
118510             prefix = Ext.baseCSSPrefix;
118511         
118512         inner = inner.replace(prefix + 'grid-row', prefix + 'grid-row-summary');
118513         inner = inner.replace('{{id}}', '{gridSummaryValue}');
118514         inner = inner.replace(this.nestedIdRe, '{id$1}');  
118515         inner = inner.replace('{[this.embedRowCls()]}', '{rowCls}');
118516         inner = inner.replace('{[this.embedRowAttr()]}', '{rowAttr}');
118517         inner = Ext.create('Ext.XTemplate', inner, {
118518             firstOrLastCls: Ext.view.TableChunker.firstOrLastCls
118519         });
118520         
118521         return inner.applyTemplate({
118522             columns: this.getPrintData(index)
118523         });
118524     },
118525     
118526     /**
118527      * Gets the value for the column from the attached data.
118528      * @param {Ext.grid.column.Column} column The header
118529      * @param {Object} data The current data
118530      * @return {String} The value to be rendered
118531      */
118532     getColumnValue: function(column, summaryData){
118533         var comp     = Ext.getCmp(column.id),
118534             value    = summaryData[column.id],
118535             renderer = comp.summaryRenderer;
118536
118537         if (renderer) {
118538             value = renderer.call(
118539                 comp.scope || this,
118540                 value,
118541                 summaryData,
118542                 column.dataIndex
118543             );
118544         }
118545         return value;
118546     },
118547     
118548     /**
118549      * Get the summary data for a field.
118550      * @private
118551      * @param {Ext.data.Store} store The store to get the data from
118552      * @param {String/Function} type The type of aggregation. If a function is specified it will
118553      * be passed to the stores aggregate function.
118554      * @param {String} field The field to aggregate on
118555      * @param {Boolean} group True to aggregate in grouped mode 
118556      * @return {Number/String/Object} See the return type for the store functions.
118557      */
118558     getSummary: function(store, type, field, group){
118559         if (type) {
118560             if (Ext.isFunction(type)) {
118561                 return store.aggregate(type, null, group);
118562             }
118563             
118564             switch (type) {
118565                 case 'count':
118566                     return store.count(group);
118567                 case 'min':
118568                     return store.min(field, group);
118569                 case 'max':
118570                     return store.max(field, group);
118571                 case 'sum':
118572                     return store.sum(field, group);
118573                 case 'average':
118574                     return store.average(field, group);
118575                 default:
118576                     return group ? {} : '';
118577                     
118578             }
118579         }
118580     }
118581     
118582 });
118583
118584 /**
118585  * @class Ext.grid.feature.Chunking
118586  * @extends Ext.grid.feature.Feature
118587  */
118588 Ext.define('Ext.grid.feature.Chunking', {
118589     extend: 'Ext.grid.feature.Feature',
118590     alias: 'feature.chunking',
118591     
118592     chunkSize: 20,
118593     rowHeight: Ext.isIE ? 27 : 26,
118594     visibleChunk: 0,
118595     hasFeatureEvent: false,
118596     attachEvents: function() {
118597         var grid = this.view.up('gridpanel'),
118598             scroller = grid.down('gridscroller[dock=right]');
118599         scroller.el.on('scroll', this.onBodyScroll, this, {buffer: 300});
118600         //this.view.on('bodyscroll', this.onBodyScroll, this, {buffer: 300});
118601     },
118602     
118603     onBodyScroll: function(e, t) {
118604         var view = this.view,
118605             top  = t.scrollTop,
118606             nextChunk = Math.floor(top / this.rowHeight / this.chunkSize);
118607         if (nextChunk !== this.visibleChunk) {
118608         
118609             this.visibleChunk = nextChunk;
118610             view.refresh();
118611             view.el.dom.scrollTop = top;
118612             //BrowserBug: IE6,7,8 quirks mode takes setting scrollTop 2x.
118613             view.el.dom.scrollTop = top;
118614         }
118615     },
118616     
118617     collectData: function(records, preppedRecords, startIndex, fullWidth, orig) {
118618         var o = {
118619             fullWidth: orig.fullWidth,
118620             chunks: []
118621         },
118622         //headerCt = this.view.headerCt,
118623         //colums = headerCt.getColumnsForTpl(),
118624         recordCount = orig.rows.length,
118625         start = 0,
118626         i = 0,
118627         visibleChunk = this.visibleChunk,
118628         chunk,
118629         rows,
118630         chunkLength;
118631
118632         for (; start < recordCount; start+=this.chunkSize, i++) {
118633             if (start+this.chunkSize > recordCount) {
118634                 chunkLength = recordCount - start;
118635             } else {
118636                 chunkLength = this.chunkSize;
118637             }
118638             
118639             if (i >= visibleChunk - 1 && i <= visibleChunk + 1) {
118640                 rows = orig.rows.slice(start, start+this.chunkSize);
118641             } else {
118642                 rows = [];
118643             }
118644             o.chunks.push({
118645                 rows: rows,
118646                 fullWidth: fullWidth,
118647                 chunkHeight: chunkLength * this.rowHeight
118648             });
118649         }
118650         
118651         
118652         return o;
118653     },
118654     
118655     getTableFragments: function() {
118656         return {
118657             openTableWrap: function() {
118658                 return '<tpl for="chunks"><div class="' + Ext.baseCSSPrefix + 'grid-chunk" style="height: {chunkHeight}px;">';
118659             },
118660             closeTableWrap: function() {
118661                 return '</div></tpl>';
118662             }
118663         };
118664     }
118665 });
118666
118667 /**
118668  * @class Ext.grid.feature.Grouping
118669  * @extends Ext.grid.feature.Feature
118670  * 
118671  * This feature allows to display the grid rows aggregated into groups as specified by the {@link Ext.data.Store#groupers}
118672  * specified on the Store. The group will show the title for the group name and then the appropriate records for the group
118673  * underneath. The groups can also be expanded and collapsed.
118674  * 
118675  * ## Extra Events
118676  * This feature adds several extra events that will be fired on the grid to interact with the groups:
118677  *
118678  *  - {@link #groupclick}
118679  *  - {@link #groupdblclick}
118680  *  - {@link #groupcontextmenu}
118681  *  - {@link #groupexpand}
118682  *  - {@link #groupcollapse}
118683  * 
118684  * ## Menu Augmentation
118685  * This feature adds extra options to the grid column menu to provide the user with functionality to modify the grouping.
118686  * This can be disabled by setting the {@link #enableGroupingMenu} option. The option to disallow grouping from being turned off
118687  * by thew user is {@link #enableNoGroups}.
118688  * 
118689  * ## Controlling Group Text
118690  * The {@link #groupHeaderTpl} is used to control the rendered title for each group. It can modified to customized
118691  * the default display.
118692  * 
118693  * ## Example Usage
118694  * 
118695  *     var groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
118696  *         groupHeaderTpl: 'Group: {name} ({rows.length})', //print the number of items in the group
118697  *         startCollapsed: true // start all groups collapsed
118698  *     });
118699  * 
118700  * @ftype grouping
118701  * @author Nicolas Ferrero
118702  */
118703 Ext.define('Ext.grid.feature.Grouping', {
118704     extend: 'Ext.grid.feature.Feature',
118705     alias: 'feature.grouping',
118706
118707     eventPrefix: 'group',
118708     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-group-hd',
118709
118710     constructor: function() {
118711         var me = this;
118712         
118713         me.collapsedState = {};
118714         me.callParent(arguments);
118715     },
118716     
118717     /**
118718      * @event groupclick
118719      * @param {Ext.view.Table} view
118720      * @param {HTMLElement} node
118721      * @param {String} group The name of the group
118722      * @param {Ext.EventObject} e
118723      */
118724
118725     /**
118726      * @event groupdblclick
118727      * @param {Ext.view.Table} view
118728      * @param {HTMLElement} node
118729      * @param {String} group The name of the group
118730      * @param {Ext.EventObject} e
118731      */
118732
118733     /**
118734      * @event groupcontextmenu
118735      * @param {Ext.view.Table} view
118736      * @param {HTMLElement} node
118737      * @param {String} group The name of the group
118738      * @param {Ext.EventObject} e
118739      */
118740
118741     /**
118742      * @event groupcollapse
118743      * @param {Ext.view.Table} view
118744      * @param {HTMLElement} node
118745      * @param {String} group The name of the group
118746      * @param {Ext.EventObject} e
118747      */
118748
118749     /**
118750      * @event groupexpand
118751      * @param {Ext.view.Table} view
118752      * @param {HTMLElement} node
118753      * @param {String} group The name of the group
118754      * @param {Ext.EventObject} e
118755      */
118756
118757     /**
118758      * @cfg {String} groupHeaderTpl
118759      * Template snippet, this cannot be an actual template. {name} will be replaced with the current group.
118760      * Defaults to 'Group: {name}'
118761      */
118762     groupHeaderTpl: 'Group: {name}',
118763
118764     /**
118765      * @cfg {Number} depthToIndent
118766      * Number of pixels to indent per grouping level
118767      */
118768     depthToIndent: 17,
118769
118770     collapsedCls: Ext.baseCSSPrefix + 'grid-group-collapsed',
118771     hdCollapsedCls: Ext.baseCSSPrefix + 'grid-group-hd-collapsed',
118772
118773     /**
118774      * @cfg {String} groupByText Text displayed in the grid header menu for grouping by header.
118775      */
118776     groupByText : 'Group By This Field',
118777     /**
118778      * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping.
118779      */
118780     showGroupsText : 'Show in Groups',
118781
118782     /**
118783      * @cfg {Boolean} hideGroupedHeader<tt>true</tt> to hide the header that is currently grouped.
118784      */
118785     hideGroupedHeader : false,
118786
118787     /**
118788      * @cfg {Boolean} startCollapsed <tt>true</tt> to start all groups collapsed
118789      */
118790     startCollapsed : false,
118791
118792     /**
118793      * @cfg {Boolean} enableGroupingMenu <tt>true</tt> to enable the grouping control in the header menu
118794      */
118795     enableGroupingMenu : true,
118796
118797     /**
118798      * @cfg {Boolean} enableNoGroups <tt>true</tt> to allow the user to turn off grouping
118799      */
118800     enableNoGroups : true,
118801     
118802     enable: function() {
118803         var me    = this,
118804             view  = me.view,
118805             store = view.store,
118806             groupToggleMenuItem;
118807             
118808         me.lastGroupField = me.getGroupField();
118809
118810         if (me.lastGroupIndex) {
118811             store.group(me.lastGroupIndex);
118812         }
118813         me.callParent();
118814         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
118815         groupToggleMenuItem.setChecked(true, true);
118816         me.refreshIf();
118817     },
118818
118819     disable: function() {
118820         var me    = this,
118821             view  = me.view,
118822             store = view.store,
118823             remote = store.remoteGroup,
118824             groupToggleMenuItem,
118825             lastGroup;
118826             
118827         lastGroup = store.groupers.first();
118828         if (lastGroup) {
118829             me.lastGroupIndex = lastGroup.property;
118830             me.block();
118831             store.clearGrouping();
118832             me.unblock();
118833         }
118834         
118835         me.callParent();
118836         groupToggleMenuItem = me.view.headerCt.getMenu().down('#groupToggleMenuItem');
118837         groupToggleMenuItem.setChecked(true, true);
118838         groupToggleMenuItem.setChecked(false, true);
118839         if (!remote) {
118840             view.refresh();
118841         }
118842     },
118843     
118844     refreshIf: function() {
118845         if (this.blockRefresh !== true) {
118846             this.view.refresh();
118847         }    
118848     },
118849
118850     getFeatureTpl: function(values, parent, x, xcount) {
118851         var me = this;
118852         
118853         return [
118854             '<tpl if="typeof rows !== \'undefined\'">',
118855                 // group row tpl
118856                 '<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>',
118857                 // this is the rowbody
118858                 '<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>',
118859             '</tpl>'
118860         ].join('');
118861     },
118862
118863     getFragmentTpl: function() {
118864         return {
118865             indentByDepth: this.indentByDepth,
118866             depthToIndent: this.depthToIndent
118867         };
118868     },
118869
118870     indentByDepth: function(values) {
118871         var depth = values.depth || 0;
118872         return 'style="padding-left:'+ depth * this.depthToIndent + 'px;"';
118873     },
118874
118875     // Containers holding these components are responsible for
118876     // destroying them, we are just deleting references.
118877     destroy: function() {
118878         var me = this;
118879         
118880         delete me.view;
118881         delete me.prunedHeader;
118882     },
118883
118884     // perhaps rename to afterViewRender
118885     attachEvents: function() {
118886         var me = this,
118887             view = me.view;
118888
118889         view.on({
118890             scope: me,
118891             groupclick: me.onGroupClick,
118892             rowfocus: me.onRowFocus
118893         });
118894         view.store.on('groupchange', me.onGroupChange, me);
118895
118896         me.pruneGroupedHeader();
118897
118898         if (me.enableGroupingMenu) {
118899             me.injectGroupingMenu();
118900         }
118901         me.lastGroupField = me.getGroupField();
118902         me.block();
118903         me.onGroupChange();
118904         me.unblock();
118905     },
118906     
118907     injectGroupingMenu: function() {
118908         var me       = this,
118909             view     = me.view,
118910             headerCt = view.headerCt;
118911         headerCt.showMenuBy = me.showMenuBy;
118912         headerCt.getMenuItems = me.getMenuItems();
118913     },
118914     
118915     showMenuBy: function(t, header) {
118916         var menu = this.getMenu(),
118917             groupMenuItem  = menu.down('#groupMenuItem'),
118918             groupableMth = header.groupable === false ?  'disable' : 'enable';
118919             
118920         groupMenuItem[groupableMth]();
118921         Ext.grid.header.Container.prototype.showMenuBy.apply(this, arguments);
118922     },
118923     
118924     getMenuItems: function() {
118925         var me                 = this,
118926             groupByText        = me.groupByText,
118927             disabled           = me.disabled,
118928             showGroupsText     = me.showGroupsText,
118929             enableNoGroups     = me.enableNoGroups,
118930             groupMenuItemClick = Ext.Function.bind(me.onGroupMenuItemClick, me),
118931             groupToggleMenuItemClick = Ext.Function.bind(me.onGroupToggleMenuItemClick, me);
118932         
118933         // runs in the scope of headerCt
118934         return function() {
118935             var o = Ext.grid.header.Container.prototype.getMenuItems.call(this);
118936             o.push('-', {
118937                 iconCls: Ext.baseCSSPrefix + 'group-by-icon',
118938                 itemId: 'groupMenuItem',
118939                 text: groupByText,
118940                 handler: groupMenuItemClick
118941             });
118942             if (enableNoGroups) {
118943                 o.push({
118944                     itemId: 'groupToggleMenuItem',
118945                     text: showGroupsText,
118946                     checked: !disabled,
118947                     checkHandler: groupToggleMenuItemClick
118948                 });
118949             }
118950             return o;
118951         };
118952     },
118953
118954
118955     /**
118956      * Group by the header the user has clicked on.
118957      * @private
118958      */
118959     onGroupMenuItemClick: function(menuItem, e) {
118960         var me = this,
118961             menu = menuItem.parentMenu,
118962             hdr  = menu.activeHeader,
118963             view = me.view,
118964             store = view.store,
118965             remote = store.remoteGroup;
118966
118967         delete me.lastGroupIndex;
118968         me.block();
118969         me.enable();
118970         store.group(hdr.dataIndex);
118971         me.pruneGroupedHeader();
118972         me.unblock();
118973         if (!remote) {
118974             view.refresh();
118975         }  
118976     },
118977     
118978     block: function(){
118979         this.blockRefresh = this.view.blockRefresh = true;
118980     },
118981     
118982     unblock: function(){
118983         this.blockRefresh = this.view.blockRefresh = false;
118984     },
118985
118986     /**
118987      * Turn on and off grouping via the menu
118988      * @private
118989      */
118990     onGroupToggleMenuItemClick: function(menuItem, checked) {
118991         this[checked ? 'enable' : 'disable']();
118992     },
118993
118994     /**
118995      * Prunes the grouped header from the header container
118996      * @private
118997      */
118998     pruneGroupedHeader: function() {
118999         var me         = this,
119000             view       = me.view,
119001             store      = view.store,
119002             groupField = me.getGroupField(),
119003             headerCt   = view.headerCt,
119004             header     = headerCt.down('header[dataIndex=' + groupField + ']');
119005
119006         if (header) {
119007             if (me.prunedHeader) {
119008                 me.prunedHeader.show();
119009             }
119010             me.prunedHeader = header;
119011             header.hide();
119012         }
119013     },
119014
119015     getGroupField: function(){
119016         var group = this.view.store.groupers.first();
119017         if (group) {
119018             return group.property;    
119019         }
119020         return ''; 
119021     },
119022
119023     /**
119024      * When a row gains focus, expand the groups above it
119025      * @private
119026      */
119027     onRowFocus: function(rowIdx) {
119028         var node    = this.view.getNode(rowIdx),
119029             groupBd = Ext.fly(node).up('.' + this.collapsedCls);
119030
119031         if (groupBd) {
119032             // for multiple level groups, should expand every groupBd
119033             // above
119034             this.expand(groupBd);
119035         }
119036     },
119037
119038     /**
119039      * Expand a group by the groupBody
119040      * @param {Ext.Element} groupBd
119041      * @private
119042      */
119043     expand: function(groupBd) {
119044         var me = this,
119045             view = me.view,
119046             grid = view.up('gridpanel'),
119047             groupBdDom = Ext.getDom(groupBd);
119048             
119049         me.collapsedState[groupBdDom.id] = false;
119050
119051         groupBd.removeCls(me.collapsedCls);
119052         groupBd.prev().removeCls(me.hdCollapsedCls);
119053
119054         grid.determineScrollbars();
119055         grid.invalidateScroller();
119056         view.fireEvent('groupexpand');
119057     },
119058
119059     /**
119060      * Collapse a group by the groupBody
119061      * @param {Ext.Element} groupBd
119062      * @private
119063      */
119064     collapse: function(groupBd) {
119065         var me = this,
119066             view = me.view,
119067             grid = view.up('gridpanel'),
119068             groupBdDom = Ext.getDom(groupBd);
119069             
119070         me.collapsedState[groupBdDom.id] = true;
119071
119072         groupBd.addCls(me.collapsedCls);
119073         groupBd.prev().addCls(me.hdCollapsedCls);
119074
119075         grid.determineScrollbars();
119076         grid.invalidateScroller();
119077         view.fireEvent('groupcollapse');
119078     },
119079     
119080     onGroupChange: function(){
119081         var me = this,
119082             field = me.getGroupField(),
119083             menuItem;
119084             
119085         if (me.hideGroupedHeader) {
119086             if (me.lastGroupField) {
119087                 menuItem = me.getMenuItem(me.lastGroupField);
119088                 if (menuItem) {
119089                     menuItem.setChecked(true);
119090                 }
119091             }
119092             if (field) {
119093                 menuItem = me.getMenuItem(field);
119094                 if (menuItem) {
119095                     menuItem.setChecked(false);
119096                 }
119097             }
119098         }
119099         if (me.blockRefresh !== true) {
119100             me.view.refresh();
119101         }
119102         me.lastGroupField = field;
119103     },
119104     
119105     /**
119106      * Gets the related menu item for a dataIndex
119107      * @private
119108      * @return {Ext.grid.header.Container} The header
119109      */
119110     getMenuItem: function(dataIndex){
119111         var view = this.view,
119112             header = view.headerCt.down('gridcolumn[dataIndex=' + dataIndex + ']'),
119113             menu = view.headerCt.getMenu();
119114             
119115         return menu.down('menuitem[headerId='+ header.id +']');
119116     },
119117
119118     /**
119119      * Toggle between expanded/collapsed state when clicking on
119120      * the group.
119121      * @private
119122      */
119123     onGroupClick: function(view, group, idx, foo, e) {
119124         var me = this,
119125             toggleCls = me.toggleCls,
119126             groupBd = Ext.fly(group.nextSibling, '_grouping');
119127
119128         if (groupBd.hasCls(me.collapsedCls)) {
119129             me.expand(groupBd);
119130         } else {
119131             me.collapse(groupBd);
119132         }
119133     },
119134
119135     // Injects isRow and closeRow into the metaRowTpl.
119136     getMetaRowTplFragments: function() {
119137         return {
119138             isRow: this.isRow,
119139             closeRow: this.closeRow
119140         };
119141     },
119142
119143     // injected into rowtpl and wrapped around metaRowTpl
119144     // becomes part of the standard tpl
119145     isRow: function() {
119146         return '<tpl if="typeof rows === \'undefined\'">';
119147     },
119148
119149     // injected into rowtpl and wrapped around metaRowTpl
119150     // becomes part of the standard tpl
119151     closeRow: function() {
119152         return '</tpl>';
119153     },
119154
119155     // isRow and closeRow are injected via getMetaRowTplFragments
119156     mutateMetaRowTpl: function(metaRowTpl) {
119157         metaRowTpl.unshift('{[this.isRow()]}');
119158         metaRowTpl.push('{[this.closeRow()]}');
119159     },
119160
119161     // injects an additional style attribute via tdAttrKey with the proper
119162     // amount of padding
119163     getAdditionalData: function(data, idx, record, orig) {
119164         var view = this.view,
119165             hCt  = view.headerCt,
119166             col  = hCt.items.getAt(0),
119167             o = {},
119168             tdAttrKey = col.id + '-tdAttr';
119169
119170         // maintain the current tdAttr that a user may ahve set.
119171         o[tdAttrKey] = this.indentByDepth(data) + " " + (orig[tdAttrKey] ? orig[tdAttrKey] : '');
119172         o.collapsed = 'true';
119173         return o;
119174     },
119175
119176     // return matching preppedRecords
119177     getGroupRows: function(group, records, preppedRecords, fullWidth) {
119178         var me = this,
119179             children = group.children,
119180             rows = group.rows = [],
119181             view = me.view;
119182         group.viewId = view.id;
119183
119184         Ext.Array.each(records, function(record, idx) {
119185             if (Ext.Array.indexOf(children, record) != -1) {
119186                 rows.push(Ext.apply(preppedRecords[idx], {
119187                     depth: 1
119188                 }));
119189             }
119190         });
119191         delete group.children;
119192         group.fullWidth = fullWidth;
119193         if (me.collapsedState[view.id + '-gp-' + group.name]) {
119194             group.collapsedCls = me.collapsedCls;
119195             group.hdCollapsedCls = me.hdCollapsedCls;
119196         }
119197
119198         return group;
119199     },
119200
119201     // return the data in a grouped format.
119202     collectData: function(records, preppedRecords, startIndex, fullWidth, o) {
119203         var me    = this,
119204             store = me.view.store,
119205             groups;
119206             
119207         if (!me.disabled && store.isGrouped()) {
119208             groups = store.getGroups();
119209             Ext.Array.each(groups, function(group, idx){
119210                 me.getGroupRows(group, records, preppedRecords, fullWidth);
119211             }, me);
119212             return {
119213                 rows: groups,
119214                 fullWidth: fullWidth
119215             };
119216         }
119217         return o;
119218     },
119219     
119220     // adds the groupName to the groupclick, groupdblclick, groupcontextmenu
119221     // events that are fired on the view. Chose not to return the actual
119222     // group itself because of its expense and because developers can simply
119223     // grab the group via store.getGroups(groupName)
119224     getFireEventArgs: function(type, view, featureTarget, e) {
119225         var returnArray = [type, view, featureTarget],
119226             groupBd     = Ext.fly(featureTarget.nextSibling, '_grouping'),
119227             groupBdId   = Ext.getDom(groupBd).id,
119228             prefix      = view.id + '-gp-',
119229             groupName   = groupBdId.substr(prefix.length);
119230         
119231         returnArray.push(groupName, e);
119232         
119233         return returnArray;
119234     }
119235 });
119236
119237 /**
119238  * @class Ext.grid.feature.GroupingSummary
119239  * @extends Ext.grid.feature.Grouping
119240  *
119241  * This feature adds an aggregate summary row at the bottom of each group that is provided
119242  * by the {@link Ext.grid.feature.Grouping} feature. There are two aspects to the summary:
119243  *
119244  * ## Calculation
119245  *
119246  * The summary value needs to be calculated for each column in the grid. This is controlled
119247  * by the summaryType option specified on the column. There are several built in summary types,
119248  * which can be specified as a string on the column configuration. These call underlying methods
119249  * on the store:
119250  *
119251  *  - {@link Ext.data.Store#count count}
119252  *  - {@link Ext.data.Store#sum sum}
119253  *  - {@link Ext.data.Store#min min}
119254  *  - {@link Ext.data.Store#max max}
119255  *  - {@link Ext.data.Store#average average}
119256  *
119257  * Alternatively, the summaryType can be a function definition. If this is the case,
119258  * the function is called with an array of records to calculate the summary value.
119259  *
119260  * ## Rendering
119261  *
119262  * Similar to a column, the summary also supports a summaryRenderer function. This
119263  * summaryRenderer is called before displaying a value. The function is optional, if
119264  * not specified the default calculated value is shown. The summaryRenderer is called with:
119265  *
119266  *  - value {Object} - The calculated value.
119267  *  - summaryData {Object} - Contains all raw summary values for the row.
119268  *  - field {String} - The name of the field we are calculating
119269  *
119270  * ## Example Usage
119271  *
119272  *     @example
119273  *     Ext.define('TestResult', {
119274  *         extend: 'Ext.data.Model',
119275  *         fields: ['student', 'subject', {
119276  *             name: 'mark',
119277  *             type: 'int'
119278  *         }]
119279  *     });
119280  *
119281  *     Ext.create('Ext.grid.Panel', {
119282  *         width: 200,
119283  *         height: 240,
119284  *         renderTo: document.body,
119285  *         features: [{
119286  *             groupHeaderTpl: 'Subject: {name}',
119287  *             ftype: 'groupingsummary'
119288  *         }],
119289  *         store: {
119290  *             model: 'TestResult',
119291  *             groupField: 'subject',
119292  *             data: [{
119293  *                 student: 'Student 1',
119294  *                 subject: 'Math',
119295  *                 mark: 84
119296  *             },{
119297  *                 student: 'Student 1',
119298  *                 subject: 'Science',
119299  *                 mark: 72
119300  *             },{
119301  *                 student: 'Student 2',
119302  *                 subject: 'Math',
119303  *                 mark: 96
119304  *             },{
119305  *                 student: 'Student 2',
119306  *                 subject: 'Science',
119307  *                 mark: 68
119308  *             }]
119309  *         },
119310  *         columns: [{
119311  *             dataIndex: 'student',
119312  *             text: 'Name',
119313  *             summaryType: 'count',
119314  *             summaryRenderer: function(value){
119315  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : '');
119316  *             }
119317  *         }, {
119318  *             dataIndex: 'mark',
119319  *             text: 'Mark',
119320  *             summaryType: 'average'
119321  *         }]
119322  *     });
119323  */
119324 Ext.define('Ext.grid.feature.GroupingSummary', {
119325
119326     /* Begin Definitions */
119327
119328     extend: 'Ext.grid.feature.Grouping',
119329
119330     alias: 'feature.groupingsummary',
119331
119332     mixins: {
119333         summary: 'Ext.grid.feature.AbstractSummary'
119334     },
119335
119336     /* End Definitions */
119337
119338
119339    /**
119340     * Modifies the row template to include the summary row.
119341     * @private
119342     * @return {String} The modified template
119343     */
119344    getFeatureTpl: function() {
119345         var tpl = this.callParent(arguments);
119346
119347         if (this.showSummaryRow) {
119348             // lop off the end </tpl> so we can attach it
119349             tpl = tpl.replace('</tpl>', '');
119350             tpl += '{[this.printSummaryRow(xindex)]}</tpl>';
119351         }
119352         return tpl;
119353     },
119354
119355     /**
119356      * Gets any fragments needed for the template.
119357      * @private
119358      * @return {Object} The fragments
119359      */
119360     getFragmentTpl: function() {
119361         var me = this,
119362             fragments = me.callParent();
119363
119364         Ext.apply(fragments, me.getSummaryFragments());
119365         if (me.showSummaryRow) {
119366             // this gets called before render, so we'll setup the data here.
119367             me.summaryGroups = me.view.store.getGroups();
119368             me.summaryData = me.generateSummaryData();
119369         }
119370         return fragments;
119371     },
119372
119373     /**
119374      * Gets the data for printing a template row
119375      * @private
119376      * @param {Number} index The index in the template
119377      * @return {Array} The template values
119378      */
119379     getPrintData: function(index){
119380         var me = this,
119381             columns = me.view.headerCt.getColumnsForTpl(),
119382             i = 0,
119383             length = columns.length,
119384             data = [],
119385             name = me.summaryGroups[index - 1].name,
119386             active = me.summaryData[name],
119387             column;
119388
119389         for (; i < length; ++i) {
119390             column = columns[i];
119391             column.gridSummaryValue = this.getColumnValue(column, active);
119392             data.push(column);
119393         }
119394         return data;
119395     },
119396
119397     /**
119398      * Generates all of the summary data to be used when processing the template
119399      * @private
119400      * @return {Object} The summary data
119401      */
119402     generateSummaryData: function(){
119403         var me = this,
119404             data = {},
119405             remoteData = {},
119406             store = me.view.store,
119407             groupField = this.getGroupField(),
119408             reader = store.proxy.reader,
119409             groups = me.summaryGroups,
119410             columns = me.view.headerCt.getColumnsForTpl(),
119411             remote,
119412             i,
119413             length,
119414             fieldData,
119415             root,
119416             key,
119417             comp;
119418
119419         for (i = 0, length = groups.length; i < length; ++i) {
119420             data[groups[i].name] = {};
119421         }
119422
119423         /**
119424          * @cfg {String} [remoteRoot=undefined]  The name of the property which contains the Array of
119425          * summary objects. It allows to use server-side calculated summaries.
119426          */
119427         if (me.remoteRoot && reader.rawData) {
119428             // reset reader root and rebuild extractors to extract summaries data
119429             root = reader.root;
119430             reader.root = me.remoteRoot;
119431             reader.buildExtractors(true);
119432             Ext.Array.each(reader.getRoot(reader.rawData), function(value) {
119433                  remoteData[value[groupField]] = value;
119434             });
119435             // restore initial reader configuration
119436             reader.root = root;
119437             reader.buildExtractors(true);
119438         }
119439
119440         for (i = 0, length = columns.length; i < length; ++i) {
119441             comp = Ext.getCmp(columns[i].id);
119442             fieldData = me.getSummary(store, comp.summaryType, comp.dataIndex, true);
119443
119444             for (key in fieldData) {
119445                 if (fieldData.hasOwnProperty(key)) {
119446                     data[key][comp.id] = fieldData[key];
119447                 }
119448             }
119449
119450             for (key in remoteData) {
119451                 if (remoteData.hasOwnProperty(key)) {
119452                     remote = remoteData[key][comp.dataIndex];
119453                     if (remote !== undefined && data[key] !== undefined) {
119454                         data[key][comp.id] = remote;
119455                     }
119456                 }
119457             }
119458         }
119459         return data;
119460     }
119461 });
119462
119463 /**
119464  * @class Ext.grid.feature.RowBody
119465  * @extends Ext.grid.feature.Feature
119466  *
119467  * The rowbody feature enhances the grid's markup to have an additional
119468  * tr -> td -> div which spans the entire width of the original row.
119469  *
119470  * This is useful to to associate additional information with a particular
119471  * record in a grid.
119472  *
119473  * Rowbodies are initially hidden unless you override getAdditionalData.
119474  *
119475  * Will expose additional events on the gridview with the prefix of 'rowbody'.
119476  * For example: 'rowbodyclick', 'rowbodydblclick', 'rowbodycontextmenu'.
119477  *
119478  * @ftype rowbody
119479  */
119480 Ext.define('Ext.grid.feature.RowBody', {
119481     extend: 'Ext.grid.feature.Feature',
119482     alias: 'feature.rowbody',
119483     rowBodyHiddenCls: Ext.baseCSSPrefix + 'grid-row-body-hidden',
119484     rowBodyTrCls: Ext.baseCSSPrefix + 'grid-rowbody-tr',
119485     rowBodyTdCls: Ext.baseCSSPrefix + 'grid-cell-rowbody',
119486     rowBodyDivCls: Ext.baseCSSPrefix + 'grid-rowbody',
119487
119488     eventPrefix: 'rowbody',
119489     eventSelector: '.' + Ext.baseCSSPrefix + 'grid-rowbody-tr',
119490     
119491     getRowBody: function(values) {
119492         return [
119493             '<tr class="' + this.rowBodyTrCls + ' {rowBodyCls}">',
119494                 '<td class="' + this.rowBodyTdCls + '" colspan="{rowBodyColspan}">',
119495                     '<div class="' + this.rowBodyDivCls + '">{rowBody}</div>',
119496                 '</td>',
119497             '</tr>'
119498         ].join('');
119499     },
119500     
119501     // injects getRowBody into the metaRowTpl.
119502     getMetaRowTplFragments: function() {
119503         return {
119504             getRowBody: this.getRowBody,
119505             rowBodyTrCls: this.rowBodyTrCls,
119506             rowBodyTdCls: this.rowBodyTdCls,
119507             rowBodyDivCls: this.rowBodyDivCls
119508         };
119509     },
119510
119511     mutateMetaRowTpl: function(metaRowTpl) {
119512         metaRowTpl.push('{[this.getRowBody(values)]}');
119513     },
119514
119515     /**
119516      * Provide additional data to the prepareData call within the grid view.
119517      * The rowbody feature adds 3 additional variables into the grid view's template.
119518      * These are rowBodyCls, rowBodyColspan, and rowBody.
119519      * @param {Object} data The data for this particular record.
119520      * @param {Number} idx The row index for this record.
119521      * @param {Ext.data.Model} record The record instance
119522      * @param {Object} orig The original result from the prepareData call to massage.
119523      */
119524     getAdditionalData: function(data, idx, record, orig) {
119525         var headerCt = this.view.headerCt,
119526             colspan  = headerCt.getColumnCount();
119527
119528         return {
119529             rowBody: "",
119530             rowBodyCls: this.rowBodyCls,
119531             rowBodyColspan: colspan
119532         };
119533     }
119534 });
119535 /**
119536  * @class Ext.grid.feature.RowWrap
119537  * @extends Ext.grid.feature.Feature
119538  * @private
119539  */
119540 Ext.define('Ext.grid.feature.RowWrap', {
119541     extend: 'Ext.grid.feature.Feature',
119542     alias: 'feature.rowwrap',
119543
119544     // turn off feature events.
119545     hasFeatureEvent: false,
119546     
119547     mutateMetaRowTpl: function(metaRowTpl) {        
119548         // Remove "x-grid-row" from the first row, note this could be wrong
119549         // if some other feature unshifted things in front.
119550         metaRowTpl[0] = metaRowTpl[0].replace(Ext.baseCSSPrefix + 'grid-row', '');
119551         metaRowTpl[0] = metaRowTpl[0].replace("{[this.embedRowCls()]}", "");
119552         // 2
119553         metaRowTpl.unshift('<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" style="width: {[this.embedFullWidth()]}px;">');
119554         // 1
119555         metaRowTpl.unshift('<tr class="' + Ext.baseCSSPrefix + 'grid-row {[this.embedRowCls()]}"><td colspan="{[this.embedColSpan()]}"><div class="' + Ext.baseCSSPrefix + 'grid-rowwrap-div">');
119556         
119557         // 3
119558         metaRowTpl.push('</table>');
119559         // 4
119560         metaRowTpl.push('</div></td></tr>');
119561     },
119562     
119563     embedColSpan: function() {
119564         return '{colspan}';
119565     },
119566     
119567     embedFullWidth: function() {
119568         return '{fullWidth}';
119569     },
119570     
119571     getAdditionalData: function(data, idx, record, orig) {
119572         var headerCt = this.view.headerCt,
119573             colspan  = headerCt.getColumnCount(),
119574             fullWidth = headerCt.getFullWidth(),
119575             items    = headerCt.query('gridcolumn'),
119576             itemsLn  = items.length,
119577             i = 0,
119578             o = {
119579                 colspan: colspan,
119580                 fullWidth: fullWidth
119581             },
119582             id,
119583             tdClsKey,
119584             colResizerCls;
119585
119586         for (; i < itemsLn; i++) {
119587             id = items[i].id;
119588             tdClsKey = id + '-tdCls';
119589             colResizerCls = Ext.baseCSSPrefix + 'grid-col-resizer-'+id;
119590             // give the inner td's the resizer class
119591             // while maintaining anything a user may have injected via a custom
119592             // renderer
119593             o[tdClsKey] = colResizerCls + " " + (orig[tdClsKey] ? orig[tdClsKey] : '');
119594             // TODO: Unhackify the initial rendering width's
119595             o[id+'-tdAttr'] = " style=\"width: " + (items[i].hidden ? 0 : items[i].getDesiredWidth()) + "px;\" "/* + (i === 0 ? " rowspan=\"2\"" : "")*/;
119596             if (orig[id+'-tdAttr']) {
119597                 o[id+'-tdAttr'] += orig[id+'-tdAttr'];
119598             }
119599             
119600         }
119601
119602         return o;
119603     },
119604     
119605     getMetaRowTplFragments: function() {
119606         return {
119607             embedFullWidth: this.embedFullWidth,
119608             embedColSpan: this.embedColSpan
119609         };
119610     }
119611     
119612 });
119613 /**
119614  * @class Ext.grid.feature.Summary
119615  * @extends Ext.grid.feature.AbstractSummary
119616  * 
119617  * This feature is used to place a summary row at the bottom of the grid. If using a grouping, 
119618  * see {@link Ext.grid.feature.GroupingSummary}. There are 2 aspects to calculating the summaries, 
119619  * calculation and rendering.
119620  * 
119621  * ## Calculation
119622  * The summary value needs to be calculated for each column in the grid. This is controlled
119623  * by the summaryType option specified on the column. There are several built in summary types,
119624  * which can be specified as a string on the column configuration. These call underlying methods
119625  * on the store:
119626  *
119627  *  - {@link Ext.data.Store#count count}
119628  *  - {@link Ext.data.Store#sum sum}
119629  *  - {@link Ext.data.Store#min min}
119630  *  - {@link Ext.data.Store#max max}
119631  *  - {@link Ext.data.Store#average average}
119632  *
119633  * Alternatively, the summaryType can be a function definition. If this is the case,
119634  * the function is called with an array of records to calculate the summary value.
119635  * 
119636  * ## Rendering
119637  * Similar to a column, the summary also supports a summaryRenderer function. This
119638  * summaryRenderer is called before displaying a value. The function is optional, if
119639  * not specified the default calculated value is shown. The summaryRenderer is called with:
119640  *
119641  *  - value {Object} - The calculated value.
119642  *  - summaryData {Object} - Contains all raw summary values for the row.
119643  *  - field {String} - The name of the field we are calculating
119644  * 
119645  * ## Example Usage
119646  *
119647  *     @example
119648  *     Ext.define('TestResult', {
119649  *         extend: 'Ext.data.Model',
119650  *         fields: ['student', {
119651  *             name: 'mark',
119652  *             type: 'int'
119653  *         }]
119654  *     });
119655  *     
119656  *     Ext.create('Ext.grid.Panel', {
119657  *         width: 200,
119658  *         height: 140,
119659  *         renderTo: document.body,
119660  *         features: [{
119661  *             ftype: 'summary'
119662  *         }],
119663  *         store: {
119664  *             model: 'TestResult',
119665  *             data: [{
119666  *                 student: 'Student 1',
119667  *                 mark: 84
119668  *             },{
119669  *                 student: 'Student 2',
119670  *                 mark: 72
119671  *             },{
119672  *                 student: 'Student 3',
119673  *                 mark: 96
119674  *             },{
119675  *                 student: 'Student 4',
119676  *                 mark: 68
119677  *             }]
119678  *         },
119679  *         columns: [{
119680  *             dataIndex: 'student',
119681  *             text: 'Name',
119682  *             summaryType: 'count',
119683  *             summaryRenderer: function(value, summaryData, dataIndex) {
119684  *                 return Ext.String.format('{0} student{1}', value, value !== 1 ? 's' : ''); 
119685  *             }
119686  *         }, {
119687  *             dataIndex: 'mark',
119688  *             text: 'Mark',
119689  *             summaryType: 'average'
119690  *         }]
119691  *     });
119692  */
119693 Ext.define('Ext.grid.feature.Summary', {
119694     
119695     /* Begin Definitions */
119696     
119697     extend: 'Ext.grid.feature.AbstractSummary',
119698     
119699     alias: 'feature.summary',
119700     
119701     /* End Definitions */
119702     
119703     /**
119704      * Gets any fragments needed for the template.
119705      * @private
119706      * @return {Object} The fragments
119707      */
119708     getFragmentTpl: function() {
119709         // this gets called before render, so we'll setup the data here.
119710         this.summaryData = this.generateSummaryData(); 
119711         return this.getSummaryFragments();
119712     },
119713     
119714     /**
119715      * Overrides the closeRows method on the template so we can include our own custom
119716      * footer.
119717      * @private
119718      * @return {Object} The custom fragments
119719      */
119720     getTableFragments: function(){
119721         if (this.showSummaryRow) {
119722             return {
119723                 closeRows: this.closeRows
119724             };
119725         }
119726     },
119727     
119728     /**
119729      * Provide our own custom footer for the grid.
119730      * @private
119731      * @return {String} The custom footer
119732      */
119733     closeRows: function() {
119734         return '</tpl>{[this.printSummaryRow()]}';
119735     },
119736     
119737     /**
119738      * Gets the data for printing a template row
119739      * @private
119740      * @param {Number} index The index in the template
119741      * @return {Array} The template values
119742      */
119743     getPrintData: function(index){
119744         var me = this,
119745             columns = me.view.headerCt.getColumnsForTpl(),
119746             i = 0,
119747             length = columns.length,
119748             data = [],
119749             active = me.summaryData,
119750             column;
119751             
119752         for (; i < length; ++i) {
119753             column = columns[i];
119754             column.gridSummaryValue = this.getColumnValue(column, active);
119755             data.push(column);
119756         }
119757         return data;
119758     },
119759     
119760     /**
119761      * Generates all of the summary data to be used when processing the template
119762      * @private
119763      * @return {Object} The summary data
119764      */
119765     generateSummaryData: function(){
119766         var me = this,
119767             data = {},
119768             store = me.view.store,
119769             columns = me.view.headerCt.getColumnsForTpl(),
119770             i = 0,
119771             length = columns.length,
119772             fieldData,
119773             key,
119774             comp;
119775             
119776         for (i = 0, length = columns.length; i < length; ++i) {
119777             comp = Ext.getCmp(columns[i].id);
119778             data[comp.id] = me.getSummary(store, comp.summaryType, comp.dataIndex, false);
119779         }
119780         return data;
119781     }
119782 });
119783 /**
119784  * @class Ext.grid.header.DragZone
119785  * @extends Ext.dd.DragZone
119786  * @private
119787  */
119788 Ext.define('Ext.grid.header.DragZone', {
119789     extend: 'Ext.dd.DragZone',
119790     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
119791     maxProxyWidth: 120,
119792
119793     constructor: function(headerCt) {
119794         this.headerCt = headerCt;
119795         this.ddGroup =  this.getDDGroup();
119796         this.callParent([headerCt.el]);
119797         this.proxy.el.addCls(Ext.baseCSSPrefix + 'grid-col-dd');
119798     },
119799
119800     getDDGroup: function() {
119801         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
119802     },
119803
119804     getDragData: function(e) {
119805         var header = e.getTarget('.'+this.colHeaderCls),
119806             headerCmp;
119807
119808         if (header) {
119809             headerCmp = Ext.getCmp(header.id);
119810             if (!this.headerCt.dragging && headerCmp.draggable && !(headerCmp.isOnLeftEdge(e) || headerCmp.isOnRightEdge(e))) {
119811                 var ddel = document.createElement('div');
119812                 ddel.innerHTML = Ext.getCmp(header.id).text;
119813                 return {
119814                     ddel: ddel,
119815                     header: headerCmp
119816                 };
119817             }
119818         }
119819         return false;
119820     },
119821
119822     onBeforeDrag: function() {
119823         return !(this.headerCt.dragging || this.disabled);
119824     },
119825
119826     onInitDrag: function() {
119827         this.headerCt.dragging = true;
119828         this.callParent(arguments);
119829     },
119830
119831     onDragDrop: function() {
119832         this.headerCt.dragging = false;
119833         this.callParent(arguments);
119834     },
119835
119836     afterRepair: function() {
119837         this.callParent();
119838         this.headerCt.dragging = false;
119839     },
119840
119841     getRepairXY: function() {
119842         return this.dragData.header.el.getXY();
119843     },
119844     
119845     disable: function() {
119846         this.disabled = true;
119847     },
119848     
119849     enable: function() {
119850         this.disabled = false;
119851     }
119852 });
119853
119854 /**
119855  * @class Ext.grid.header.DropZone
119856  * @extends Ext.dd.DropZone
119857  * @private
119858  */
119859 Ext.define('Ext.grid.header.DropZone', {
119860     extend: 'Ext.dd.DropZone',
119861     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
119862     proxyOffsets: [-4, -9],
119863
119864     constructor: function(headerCt){
119865         this.headerCt = headerCt;
119866         this.ddGroup = this.getDDGroup();
119867         this.callParent([headerCt.el]);
119868     },
119869
119870     getDDGroup: function() {
119871         return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
119872     },
119873
119874     getTargetFromEvent : function(e){
119875         return e.getTarget('.' + this.colHeaderCls);
119876     },
119877
119878     getTopIndicator: function() {
119879         if (!this.topIndicator) {
119880             this.topIndicator = Ext.DomHelper.append(Ext.getBody(), {
119881                 cls: "col-move-top",
119882                 html: "&#160;"
119883             }, true);
119884         }
119885         return this.topIndicator;
119886     },
119887
119888     getBottomIndicator: function() {
119889         if (!this.bottomIndicator) {
119890             this.bottomIndicator = Ext.DomHelper.append(Ext.getBody(), {
119891                 cls: "col-move-bottom",
119892                 html: "&#160;"
119893             }, true);
119894         }
119895         return this.bottomIndicator;
119896     },
119897
119898     getLocation: function(e, t) {
119899         var x      = e.getXY()[0],
119900             region = Ext.fly(t).getRegion(),
119901             pos, header;
119902
119903         if ((region.right - x) <= (region.right - region.left) / 2) {
119904             pos = "after";
119905         } else {
119906             pos = "before";
119907         }
119908         return {
119909             pos: pos,
119910             header: Ext.getCmp(t.id),
119911             node: t
119912         };
119913     },
119914
119915     positionIndicator: function(draggedHeader, node, e){
119916         var location = this.getLocation(e, node),
119917             header = location.header,
119918             pos    = location.pos,
119919             nextHd = draggedHeader.nextSibling('gridcolumn:not([hidden])'),
119920             prevHd = draggedHeader.previousSibling('gridcolumn:not([hidden])'),
119921             region, topIndicator, bottomIndicator, topAnchor, bottomAnchor,
119922             topXY, bottomXY, headerCtEl, minX, maxX;
119923
119924         // Cannot drag beyond non-draggable start column
119925         if (!header.draggable && header.getIndex() == 0) {
119926             return false;
119927         }
119928
119929         this.lastLocation = location;
119930
119931         if ((draggedHeader !== header) &&
119932             ((pos === "before" && nextHd !== header) ||
119933             (pos === "after" && prevHd !== header)) &&
119934             !header.isDescendantOf(draggedHeader)) {
119935
119936             // As we move in between different DropZones that are in the same
119937             // group (such as the case when in a locked grid), invalidateDrop
119938             // on the other dropZones.
119939             var allDropZones = Ext.dd.DragDropManager.getRelated(this),
119940                 ln = allDropZones.length,
119941                 i  = 0,
119942                 dropZone;
119943
119944             for (; i < ln; i++) {
119945                 dropZone = allDropZones[i];
119946                 if (dropZone !== this && dropZone.invalidateDrop) {
119947                     dropZone.invalidateDrop();
119948                 }
119949             }
119950
119951
119952             this.valid = true;
119953             topIndicator = this.getTopIndicator();
119954             bottomIndicator = this.getBottomIndicator();
119955             if (pos === 'before') {
119956                 topAnchor = 'tl';
119957                 bottomAnchor = 'bl';
119958             } else {
119959                 topAnchor = 'tr';
119960                 bottomAnchor = 'br';
119961             }
119962             topXY = header.el.getAnchorXY(topAnchor);
119963             bottomXY = header.el.getAnchorXY(bottomAnchor);
119964
119965             // constrain the indicators to the viewable section
119966             headerCtEl = this.headerCt.el;
119967             minX = headerCtEl.getLeft();
119968             maxX = headerCtEl.getRight();
119969
119970             topXY[0] = Ext.Number.constrain(topXY[0], minX, maxX);
119971             bottomXY[0] = Ext.Number.constrain(bottomXY[0], minX, maxX);
119972
119973             // adjust by offsets, this is to center the arrows so that they point
119974             // at the split point
119975             topXY[0] -= 4;
119976             topXY[1] -= 9;
119977             bottomXY[0] -= 4;
119978
119979             // position and show indicators
119980             topIndicator.setXY(topXY);
119981             bottomIndicator.setXY(bottomXY);
119982             topIndicator.show();
119983             bottomIndicator.show();
119984         // invalidate drop operation and hide indicators
119985         } else {
119986             this.invalidateDrop();
119987         }
119988     },
119989
119990     invalidateDrop: function() {
119991         this.valid = false;
119992         this.hideIndicators();
119993     },
119994
119995     onNodeOver: function(node, dragZone, e, data) {
119996         if (data.header.el.dom !== node) {
119997             this.positionIndicator(data.header, node, e);
119998         }
119999         return this.valid ? this.dropAllowed : this.dropNotAllowed;
120000     },
120001
120002     hideIndicators: function() {
120003         this.getTopIndicator().hide();
120004         this.getBottomIndicator().hide();
120005     },
120006
120007     onNodeOut: function() {
120008         this.hideIndicators();
120009     },
120010
120011     onNodeDrop: function(node, dragZone, e, data) {
120012         if (this.valid) {
120013             this.invalidateDrop();
120014             var hd = data.header,
120015                 lastLocation = this.lastLocation,
120016                 fromCt  = hd.ownerCt,
120017                 fromIdx = fromCt.items.indexOf(hd), // Container.items is a MixedCollection
120018                 toCt    = lastLocation.header.ownerCt,
120019                 toIdx   = toCt.items.indexOf(lastLocation.header),
120020                 headerCt = this.headerCt,
120021                 groupCt,
120022                 scrollerOwner;
120023
120024             if (lastLocation.pos === 'after') {
120025                 toIdx++;
120026             }
120027
120028             // If we are dragging in between two HeaderContainers that have had the lockable
120029             // mixin injected we will lock/unlock headers in between sections. Note that lockable
120030             // does NOT currently support grouped headers.
120031             if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && toCt.lockedCt) {
120032                 scrollerOwner = fromCt.up('[scrollerOwner]');
120033                 scrollerOwner.lock(hd, toIdx);
120034             } else if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && fromCt.lockedCt) {
120035                 scrollerOwner = fromCt.up('[scrollerOwner]');
120036                 scrollerOwner.unlock(hd, toIdx);
120037             } else {
120038                 // If dragging rightwards, then after removal, the insertion index will be one less when moving
120039                 // in between the same container.
120040                 if ((fromCt === toCt) && (toIdx > fromCt.items.indexOf(hd))) {
120041                     toIdx--;
120042                 }
120043
120044                 // Remove dragged header from where it was without destroying it or relaying its Container
120045                 if (fromCt !== toCt) {
120046                     fromCt.suspendLayout = true;
120047                     fromCt.remove(hd, false);
120048                     fromCt.suspendLayout = false;
120049                 }
120050
120051                 // Dragged the last header out of the fromCt group... The fromCt group must die
120052                 if (fromCt.isGroupHeader) {
120053                     if (!fromCt.items.getCount()) {
120054                         groupCt = fromCt.ownerCt;
120055                         groupCt.suspendLayout = true;
120056                         groupCt.remove(fromCt, false);
120057                         fromCt.el.dom.parentNode.removeChild(fromCt.el.dom);
120058                         groupCt.suspendLayout = false;
120059                     } else {
120060                         fromCt.minWidth = fromCt.getWidth() - hd.getWidth();
120061                         fromCt.setWidth(fromCt.minWidth);
120062                     }
120063                 }
120064
120065                 // Move dragged header into its drop position
120066                 toCt.suspendLayout = true;
120067                 if (fromCt === toCt) {
120068                     toCt.move(fromIdx, toIdx);
120069                 } else {
120070                     toCt.insert(toIdx, hd);
120071                 }
120072                 toCt.suspendLayout = false;
120073
120074                 // Group headers acquire the aggregate width of their child headers
120075                 // Therefore a child header may not flex; it must contribute a fixed width.
120076                 // But we restore the flex value when moving back into the main header container
120077                 if (toCt.isGroupHeader) {
120078                     hd.savedFlex = hd.flex;
120079                     delete hd.flex;
120080                     hd.width = hd.getWidth();
120081                     // When there was previously a flex, we need to ensure we don't count for the
120082                     // border twice.
120083                     toCt.minWidth = toCt.getWidth() + hd.getWidth() - (hd.savedFlex ? 1 : 0);
120084                     toCt.setWidth(toCt.minWidth);
120085                 } else {
120086                     if (hd.savedFlex) {
120087                         hd.flex = hd.savedFlex;
120088                         delete hd.width;
120089                     }
120090                 }
120091
120092
120093                 // Refresh columns cache in case we remove an emptied group column
120094                 headerCt.purgeCache();
120095                 headerCt.doLayout();
120096                 headerCt.onHeaderMoved(hd, fromIdx, toIdx);
120097                 // Emptied group header can only be destroyed after the header and grid have been refreshed
120098                 if (!fromCt.items.getCount()) {
120099                     fromCt.destroy();
120100                 }
120101             }
120102         }
120103     }
120104 });
120105
120106 /**
120107  * This class provides an abstract grid editing plugin on selected {@link Ext.grid.column.Column columns}.
120108  * The editable columns are specified by providing an {@link Ext.grid.column.Column#editor editor}
120109  * in the {@link Ext.grid.column.Column column configuration}.
120110  *
120111  * **Note:** This class should not be used directly. See {@link Ext.grid.plugin.CellEditing} and
120112  * {@link Ext.grid.plugin.RowEditing}.
120113  */
120114 Ext.define('Ext.grid.plugin.Editing', {
120115     alias: 'editing.editing',
120116
120117     requires: [
120118         'Ext.grid.column.Column',
120119         'Ext.util.KeyNav'
120120     ],
120121
120122     mixins: {
120123         observable: 'Ext.util.Observable'
120124     },
120125
120126     /**
120127      * @cfg {Number} clicksToEdit
120128      * The number of clicks on a grid required to display the editor.
120129      */
120130     clicksToEdit: 2,
120131
120132     // private
120133     defaultFieldXType: 'textfield',
120134
120135     // cell, row, form
120136     editStyle: '',
120137
120138     constructor: function(config) {
120139         var me = this;
120140         Ext.apply(me, config);
120141
120142         me.addEvents(
120143             // Doc'ed in separate editing plugins
120144             'beforeedit',
120145
120146             // Doc'ed in separate editing plugins
120147             'edit',
120148
120149             // Doc'ed in separate editing plugins
120150             'validateedit'
120151         );
120152         me.mixins.observable.constructor.call(me);
120153         // TODO: Deprecated, remove in 5.0
120154         me.relayEvents(me, ['afteredit'], 'after');
120155     },
120156
120157     // private
120158     init: function(grid) {
120159         var me = this;
120160
120161         me.grid = grid;
120162         me.view = grid.view;
120163         me.initEvents();
120164         me.mon(grid, 'reconfigure', me.onReconfigure, me);
120165         me.onReconfigure();
120166
120167         grid.relayEvents(me, ['beforeedit', 'edit', 'validateedit']);
120168         // Marks the grid as editable, so that the SelectionModel
120169         // can make appropriate decisions during navigation
120170         grid.isEditable = true;
120171         grid.editingPlugin = grid.view.editingPlugin = me;
120172     },
120173
120174     /**
120175      * Fires after the grid is reconfigured
120176      * @private
120177      */
120178     onReconfigure: function(){
120179         this.initFieldAccessors(this.view.getGridColumns());
120180     },
120181
120182     /**
120183      * @private
120184      * AbstractComponent calls destroy on all its plugins at destroy time.
120185      */
120186     destroy: function() {
120187         var me = this,
120188             grid = me.grid,
120189             headerCt = grid.headerCt,
120190             events = grid.events;
120191
120192         Ext.destroy(me.keyNav);
120193         me.removeFieldAccessors(grid.getView().getGridColumns());
120194
120195         // Clear all listeners from all our events, clear all managed listeners we added to other Observables
120196         me.clearListeners();
120197
120198         delete me.grid.editingPlugin;
120199         delete me.grid.view.editingPlugin;
120200         delete me.grid;
120201         delete me.view;
120202         delete me.editor;
120203         delete me.keyNav;
120204     },
120205
120206     // private
120207     getEditStyle: function() {
120208         return this.editStyle;
120209     },
120210
120211     // private
120212     initFieldAccessors: function(column) {
120213         var me = this;
120214
120215         if (Ext.isArray(column)) {
120216             Ext.Array.forEach(column, me.initFieldAccessors, me);
120217             return;
120218         }
120219
120220         // Augment the Header class to have a getEditor and setEditor method
120221         // Important: Only if the header does not have its own implementation.
120222         Ext.applyIf(column, {
120223             getEditor: function(record, defaultField) {
120224                 return me.getColumnField(this, defaultField);
120225             },
120226
120227             setEditor: function(field) {
120228                 me.setColumnField(this, field);
120229             }
120230         });
120231     },
120232
120233     // private
120234     removeFieldAccessors: function(column) {
120235         var me = this;
120236
120237         if (Ext.isArray(column)) {
120238             Ext.Array.forEach(column, me.removeFieldAccessors, me);
120239             return;
120240         }
120241
120242         delete column.getEditor;
120243         delete column.setEditor;
120244     },
120245
120246     // private
120247     // remaps to the public API of Ext.grid.column.Column.getEditor
120248     getColumnField: function(columnHeader, defaultField) {
120249         var field = columnHeader.field;
120250
120251         if (!field && columnHeader.editor) {
120252             field = columnHeader.editor;
120253             delete columnHeader.editor;
120254         }
120255
120256         if (!field && defaultField) {
120257             field = defaultField;
120258         }
120259
120260         if (field) {
120261             if (Ext.isString(field)) {
120262                 field = { xtype: field };
120263             }
120264             if (Ext.isObject(field) && !field.isFormField) {
120265                 field = Ext.ComponentManager.create(field, this.defaultFieldXType);
120266                 columnHeader.field = field;
120267             }
120268
120269             Ext.apply(field, {
120270                 name: columnHeader.dataIndex
120271             });
120272
120273             return field;
120274         }
120275     },
120276
120277     // private
120278     // remaps to the public API of Ext.grid.column.Column.setEditor
120279     setColumnField: function(column, field) {
120280         if (Ext.isObject(field) && !field.isFormField) {
120281             field = Ext.ComponentManager.create(field, this.defaultFieldXType);
120282         }
120283         column.field = field;
120284     },
120285
120286     // private
120287     initEvents: function() {
120288         var me = this;
120289         me.initEditTriggers();
120290         me.initCancelTriggers();
120291     },
120292
120293     // @abstract
120294     initCancelTriggers: Ext.emptyFn,
120295
120296     // private
120297     initEditTriggers: function() {
120298         var me = this,
120299             view = me.view,
120300             clickEvent = me.clicksToEdit === 1 ? 'click' : 'dblclick';
120301
120302         // Start editing
120303         me.mon(view, 'cell' + clickEvent, me.startEditByClick, me);
120304         view.on('render', function() {
120305             me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
120306                 enter: me.onEnterKey,
120307                 esc: me.onEscKey,
120308                 scope: me
120309             });
120310         }, me, { single: true });
120311     },
120312
120313     // private
120314     onEnterKey: function(e) {
120315         var me = this,
120316             grid = me.grid,
120317             selModel = grid.getSelectionModel(),
120318             record,
120319             columnHeader = grid.headerCt.getHeaderAtIndex(0);
120320
120321         // Calculate editing start position from SelectionModel
120322         // CellSelectionModel
120323         if (selModel.getCurrentPosition) {
120324             pos = selModel.getCurrentPosition();
120325             record = grid.store.getAt(pos.row);
120326             columnHeader = grid.headerCt.getHeaderAtIndex(pos.column);
120327         }
120328         // RowSelectionModel
120329         else {
120330             record = selModel.getLastSelected();
120331         }
120332         me.startEdit(record, columnHeader);
120333     },
120334
120335     // private
120336     onEscKey: function(e) {
120337         this.cancelEdit();
120338     },
120339
120340     // private
120341     startEditByClick: function(view, cell, colIdx, record, row, rowIdx, e) {
120342         this.startEdit(record, view.getHeaderAtIndex(colIdx));
120343     },
120344
120345     /**
120346      * @private
120347      * @template
120348      * Template method called before editing begins.
120349      * @param {Object} context The current editing context
120350      * @return {Boolean} Return false to cancel the editing process
120351      */
120352     beforeEdit: Ext.emptyFn,
120353
120354     /**
120355      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
120356      * @param {Ext.data.Model/Number} record The Store data record which backs the row to be edited, or index of the record in Store.
120357      * @param {Ext.grid.column.Column/Number} columnHeader The Column object defining the column to be edited, or index of the column.
120358      */
120359     startEdit: function(record, columnHeader) {
120360         var me = this,
120361             context = me.getEditingContext(record, columnHeader);
120362
120363         if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
120364             return false;
120365         }
120366
120367         me.context = context;
120368         me.editing = true;
120369     },
120370
120371     /**
120372      * @private
120373      * Collects all information necessary for any subclasses to perform their editing functions.
120374      * @param record
120375      * @param columnHeader
120376      * @returns {Object} The editing context based upon the passed record and column
120377      */
120378     getEditingContext: function(record, columnHeader) {
120379         var me = this,
120380             grid = me.grid,
120381             store = grid.store,
120382             rowIdx,
120383             colIdx,
120384             view = grid.getView(),
120385             value;
120386
120387         // If they'd passed numeric row, column indices, look them up.
120388         if (Ext.isNumber(record)) {
120389             rowIdx = record;
120390             record = store.getAt(rowIdx);
120391         } else {
120392             rowIdx = store.indexOf(record);
120393         }
120394         if (Ext.isNumber(columnHeader)) {
120395             colIdx = columnHeader;
120396             columnHeader = grid.headerCt.getHeaderAtIndex(colIdx);
120397         } else {
120398             colIdx = columnHeader.getIndex();
120399         }
120400
120401         value = record.get(columnHeader.dataIndex);
120402         return {
120403             grid: grid,
120404             record: record,
120405             field: columnHeader.dataIndex,
120406             value: value,
120407             row: view.getNode(rowIdx),
120408             column: columnHeader,
120409             rowIdx: rowIdx,
120410             colIdx: colIdx
120411         };
120412     },
120413
120414     /**
120415      * Cancels any active edit that is in progress.
120416      */
120417     cancelEdit: function() {
120418         this.editing = false;
120419     },
120420
120421     /**
120422      * Completes the edit if there is an active edit in progress.
120423      */
120424     completeEdit: function() {
120425         var me = this;
120426
120427         if (me.editing && me.validateEdit()) {
120428             me.fireEvent('edit', me.context);
120429         }
120430
120431         delete me.context;
120432         me.editing = false;
120433     },
120434
120435     // @abstract
120436     validateEdit: function() {
120437         var me = this,
120438             context = me.context;
120439
120440         return me.fireEvent('validateedit', me, context) !== false && !context.cancel;
120441     }
120442 });
120443
120444 /**
120445  * The Ext.grid.plugin.CellEditing plugin injects editing at a cell level for a Grid. Only a single
120446  * cell will be editable at a time. The field that will be used for the editor is defined at the
120447  * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
120448  *
120449  * If an editor is not specified for a particular column then that cell will not be editable and it will
120450  * be skipped when activated via the mouse or the keyboard.
120451  *
120452  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
120453  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
120454  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
120455  *
120456  *     @example
120457  *     Ext.create('Ext.data.Store', {
120458  *         storeId:'simpsonsStore',
120459  *         fields:['name', 'email', 'phone'],
120460  *         data:{'items':[
120461  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
120462  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
120463  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
120464  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
120465  *         ]},
120466  *         proxy: {
120467  *             type: 'memory',
120468  *             reader: {
120469  *                 type: 'json',
120470  *                 root: 'items'
120471  *             }
120472  *         }
120473  *     });
120474  *
120475  *     Ext.create('Ext.grid.Panel', {
120476  *         title: 'Simpsons',
120477  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
120478  *         columns: [
120479  *             {header: 'Name',  dataIndex: 'name', editor: 'textfield'},
120480  *             {header: 'Email', dataIndex: 'email', flex:1,
120481  *                 editor: {
120482  *                     xtype: 'textfield',
120483  *                     allowBlank: false
120484  *                 }
120485  *             },
120486  *             {header: 'Phone', dataIndex: 'phone'}
120487  *         ],
120488  *         selType: 'cellmodel',
120489  *         plugins: [
120490  *             Ext.create('Ext.grid.plugin.CellEditing', {
120491  *                 clicksToEdit: 1
120492  *             })
120493  *         ],
120494  *         height: 200,
120495  *         width: 400,
120496  *         renderTo: Ext.getBody()
120497  *     });
120498  */
120499 Ext.define('Ext.grid.plugin.CellEditing', {
120500     alias: 'plugin.cellediting',
120501     extend: 'Ext.grid.plugin.Editing',
120502     requires: ['Ext.grid.CellEditor', 'Ext.util.DelayedTask'],
120503
120504     constructor: function() {
120505         /**
120506          * @event beforeedit
120507          * Fires before cell editing is triggered. Return false from event handler to stop the editing.
120508          *
120509          * @param {Object} e An edit event with the following properties:
120510          *
120511          * - grid - The grid
120512          * - record - The record being edited
120513          * - field - The field name being edited
120514          * - value - The value for the field being edited.
120515          * - row - The grid table row
120516          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
120517          * - rowIdx - The row index that is being edited
120518          * - colIdx - The column index that is being edited
120519          * - cancel - Set this to true to cancel the edit or return false from your handler.
120520          */
120521         /**
120522          * @event edit
120523          * Fires after a cell is edited. Usage example:
120524          *
120525          *     grid.on('edit', function(editor, e) {
120526          *         // commit the changes right after editing finished
120527          *         e.record.commit();
120528          *     };
120529          *
120530          * @param {Ext.grid.plugin.Editing} editor
120531          * @param {Object} e An edit event with the following properties:
120532          *
120533          * - grid - The grid
120534          * - record - The record that was edited
120535          * - field - The field name that was edited
120536          * - value - The value being set
120537          * - originalValue - The original value for the field, before the edit.
120538          * - row - The grid table row
120539          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that was edited.
120540          * - rowIdx - The row index that was edited
120541          * - colIdx - The column index that was edited
120542          */
120543         /**
120544          * @event validateedit
120545          * Fires after a cell is edited, but before the value is set in the record. Return false from event handler to
120546          * cancel the change.
120547          *
120548          * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
120549          * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for
120550          * example) and then setting the field's new value in the Record directly:
120551          *
120552          *     grid.on('validateedit', function(editor, e) {
120553          *       var myTargetRow = 6;
120554          *
120555          *       if (e.row == myTargetRow) {
120556          *         e.cancel = true;
120557          *         e.record.data[e.field] = e.value;
120558          *       }
120559          *     });
120560          *
120561          * @param {Ext.grid.plugin.Editing} editor
120562          * @param {Object} e An edit event with the following properties:
120563          *
120564          * - grid - The grid
120565          * - record - The record being edited
120566          * - field - The field name being edited
120567          * - value - The value being set
120568          * - originalValue - The original value for the field, before the edit.
120569          * - row - The grid table row
120570          * - column - The grid {@link Ext.grid.column.Column Column} defining the column that is being edited.
120571          * - rowIdx - The row index that is being edited
120572          * - colIdx - The column index that is being edited
120573          * - cancel - Set this to true to cancel the edit or return false from your handler.
120574          */
120575         this.callParent(arguments);
120576         this.editors = Ext.create('Ext.util.MixedCollection', false, function(editor) {
120577             return editor.editorId;
120578         });
120579         this.editTask = Ext.create('Ext.util.DelayedTask');
120580     },
120581     
120582     onReconfigure: function(){
120583         this.editors.clear();
120584         this.callParent();    
120585     },
120586
120587     /**
120588      * @private
120589      * AbstractComponent calls destroy on all its plugins at destroy time.
120590      */
120591     destroy: function() {
120592         var me = this;
120593         me.editTask.cancel();
120594         me.editors.each(Ext.destroy, Ext);
120595         me.editors.clear();
120596         me.callParent(arguments);
120597     },
120598     
120599     onBodyScroll: function() {
120600         var ed = this.getActiveEditor();
120601         if (ed && ed.field) {
120602             if (ed.field.triggerBlur) {
120603                 ed.field.triggerBlur();
120604             } else {
120605                 ed.field.blur();
120606             }
120607         }
120608     },
120609
120610     // private
120611     // Template method called from base class's initEvents
120612     initCancelTriggers: function() {
120613         var me   = this,
120614             grid = me.grid,
120615             view = grid.view;
120616             
120617         view.addElListener('mousewheel', me.cancelEdit, me);
120618         me.mon(view, 'bodyscroll', me.onBodyScroll, me);
120619         me.mon(grid, {
120620             columnresize: me.cancelEdit,
120621             columnmove: me.cancelEdit,
120622             scope: me
120623         });
120624     },
120625
120626     /**
120627      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
120628      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
120629      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited. @override
120630      */
120631     startEdit: function(record, columnHeader) {
120632         var me = this,
120633             value = record.get(columnHeader.dataIndex),
120634             context = me.getEditingContext(record, columnHeader),
120635             ed;
120636
120637         record = context.record;
120638         columnHeader = context.column;
120639
120640         // Complete the edit now, before getting the editor's target
120641         // cell DOM element. Completing the edit causes a view refresh.
120642         me.completeEdit();
120643
120644         context.originalValue = context.value = value;
120645         if (me.beforeEdit(context) === false || me.fireEvent('beforeedit', context) === false || context.cancel) {
120646             return false;
120647         }
120648         
120649         // See if the field is editable for the requested record
120650         if (columnHeader && !columnHeader.getEditor(record)) {
120651             return false;
120652         }
120653         
120654         ed = me.getEditor(record, columnHeader);
120655         if (ed) {
120656             me.context = context;
120657             me.setActiveEditor(ed);
120658             me.setActiveRecord(record);
120659             me.setActiveColumn(columnHeader);
120660
120661             // Defer, so we have some time between view scroll to sync up the editor
120662             me.editTask.delay(15, ed.startEdit, ed, [me.getCell(record, columnHeader), value]);
120663         } else {
120664             // BrowserBug: WebKit & IE refuse to focus the element, rather
120665             // it will focus it and then immediately focus the body. This
120666             // temporary hack works for Webkit and IE6. IE7 and 8 are still
120667             // broken
120668             me.grid.getView().getEl(columnHeader).focus((Ext.isWebKit || Ext.isIE) ? 10 : false);
120669         }
120670     },
120671
120672     completeEdit: function() {
120673         var activeEd = this.getActiveEditor();
120674         if (activeEd) {
120675             activeEd.completeEdit();
120676         }
120677     },
120678
120679     // internal getters/setters
120680     setActiveEditor: function(ed) {
120681         this.activeEditor = ed;
120682     },
120683
120684     getActiveEditor: function() {
120685         return this.activeEditor;
120686     },
120687
120688     setActiveColumn: function(column) {
120689         this.activeColumn = column;
120690     },
120691
120692     getActiveColumn: function() {
120693         return this.activeColumn;
120694     },
120695
120696     setActiveRecord: function(record) {
120697         this.activeRecord = record;
120698     },
120699
120700     getActiveRecord: function() {
120701         return this.activeRecord;
120702     },
120703
120704     getEditor: function(record, column) {
120705         var me = this,
120706             editors = me.editors,
120707             editorId = column.getItemId(),
120708             editor = editors.getByKey(editorId);
120709
120710         if (editor) {
120711             return editor;
120712         } else {
120713             editor = column.getEditor(record);
120714             if (!editor) {
120715                 return false;
120716             }
120717
120718             // Allow them to specify a CellEditor in the Column
120719             if (!(editor instanceof Ext.grid.CellEditor)) {
120720                 editor = Ext.create('Ext.grid.CellEditor', {
120721                     editorId: editorId,
120722                     field: editor
120723                 });
120724             }
120725             editor.parentEl = me.grid.getEditorParent();
120726             // editor.parentEl should be set here.
120727             editor.on({
120728                 scope: me,
120729                 specialkey: me.onSpecialKey,
120730                 complete: me.onEditComplete,
120731                 canceledit: me.cancelEdit
120732             });
120733             editors.add(editor);
120734             return editor;
120735         }
120736     },
120737     
120738     // inherit docs
120739     setColumnField: function(column, field) {
120740         var ed = this.editors.getByKey(column.getItemId());
120741         Ext.destroy(ed, column.field);
120742         this.editors.removeAtKey(column.getItemId());
120743         this.callParent(arguments);
120744     },
120745
120746     /**
120747      * Gets the cell (td) for a particular record and column.
120748      * @param {Ext.data.Model} record
120749      * @param {Ext.grid.column.Column} column
120750      * @private
120751      */
120752     getCell: function(record, column) {
120753         return this.grid.getView().getCell(record, column);
120754     },
120755
120756     onSpecialKey: function(ed, field, e) {
120757         var grid = this.grid,
120758             sm;
120759         if (e.getKey() === e.TAB) {
120760             e.stopEvent();
120761             sm = grid.getSelectionModel();
120762             if (sm.onEditorTab) {
120763                 sm.onEditorTab(this, e);
120764             }
120765         }
120766     },
120767
120768     onEditComplete : function(ed, value, startValue) {
120769         var me = this,
120770             grid = me.grid,
120771             sm = grid.getSelectionModel(),
120772             activeColumn = me.getActiveColumn(),
120773             dataIndex;
120774
120775         if (activeColumn) {
120776             dataIndex = activeColumn.dataIndex;
120777
120778             me.setActiveEditor(null);
120779             me.setActiveColumn(null);
120780             me.setActiveRecord(null);
120781             delete sm.wasEditing;
120782     
120783             if (!me.validateEdit()) {
120784                 return;
120785             }
120786             // Only update the record if the new value is different than the
120787             // startValue, when the view refreshes its el will gain focus
120788             if (value !== startValue) {
120789                 me.context.record.set(dataIndex, value);
120790             // Restore focus back to the view's element.
120791             } else {
120792                 grid.getView().getEl(activeColumn).focus();
120793             }
120794             me.context.value = value;
120795             me.fireEvent('edit', me, me.context);
120796         }
120797     },
120798
120799     /**
120800      * Cancels any active editing.
120801      */
120802     cancelEdit: function() {
120803         var me = this,
120804             activeEd = me.getActiveEditor(),
120805             viewEl = me.grid.getView().getEl(me.getActiveColumn());
120806
120807         me.setActiveEditor(null);
120808         me.setActiveColumn(null);
120809         me.setActiveRecord(null);
120810         if (activeEd) {
120811             activeEd.cancelEdit();
120812             viewEl.focus();
120813         }
120814     },
120815
120816     /**
120817      * Starts editing by position (row/column)
120818      * @param {Object} position A position with keys of row and column.
120819      */
120820     startEditByPosition: function(position) {
120821         var me = this,
120822             grid = me.grid,
120823             sm = grid.getSelectionModel(),
120824             editRecord = grid.store.getAt(position.row),
120825             editColumnHeader = grid.headerCt.getHeaderAtIndex(position.column);
120826
120827         if (sm.selectByPosition) {
120828             sm.selectByPosition(position);
120829         }
120830         me.startEdit(editRecord, editColumnHeader);
120831     }
120832 });
120833 /**
120834  * This plugin provides drag and/or drop functionality for a GridView.
120835  *
120836  * It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a {@link
120837  * Ext.grid.View GridView} and loads the data object which is passed to a cooperating {@link Ext.dd.DragZone DragZone}'s
120838  * methods with the following properties:
120839  *
120840  * - `copy` : Boolean
120841  *
120842  *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` _and_
120843  *   the control key was pressed when the drag operation was begun.
120844  *
120845  * - `view` : GridView
120846  *
120847  *   The source GridView from which the drag originated.
120848  *
120849  * - `ddel` : HtmlElement
120850  *
120851  *   The drag proxy element which moves with the mouse
120852  *
120853  * - `item` : HtmlElement
120854  *
120855  *   The GridView node upon which the mousedown event was registered.
120856  *
120857  * - `records` : Array
120858  *
120859  *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
120860  *
120861  * It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are
120862  * members of the same ddGroup which processes such data objects.
120863  *
120864  * Adding this plugin to a view means that two new events may be fired from the client GridView, `{@link #beforedrop
120865  * beforedrop}` and `{@link #drop drop}`
120866  *
120867  *     @example
120868  *     Ext.create('Ext.data.Store', {
120869  *         storeId:'simpsonsStore',
120870  *         fields:['name'],
120871  *         data: [["Lisa"], ["Bart"], ["Homer"], ["Marge"]],
120872  *         proxy: {
120873  *             type: 'memory',
120874  *             reader: 'array'
120875  *         }
120876  *     });
120877  *
120878  *     Ext.create('Ext.grid.Panel', {
120879  *         store: 'simpsonsStore',
120880  *         columns: [
120881  *             {header: 'Name',  dataIndex: 'name', flex: true}
120882  *         ],
120883  *         viewConfig: {
120884  *             plugins: {
120885  *                 ptype: 'gridviewdragdrop',
120886  *                 dragText: 'Drag and drop to reorganize'
120887  *             }
120888  *         },
120889  *         height: 200,
120890  *         width: 400,
120891  *         renderTo: Ext.getBody()
120892  *     });
120893  */
120894 Ext.define('Ext.grid.plugin.DragDrop', {
120895     extend: 'Ext.AbstractPlugin',
120896     alias: 'plugin.gridviewdragdrop',
120897
120898     uses: [
120899         'Ext.view.DragZone',
120900         'Ext.grid.ViewDropZone'
120901     ],
120902
120903     /**
120904      * @event beforedrop
120905      * **This event is fired through the GridView. Add listeners to the GridView object**
120906      *
120907      * Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the GridView.
120908      *
120909      * @param {HTMLElement} node The GridView node **if any** over which the mouse was positioned.
120910      *
120911      * Returning `false` to this event signals that the drop gesture was invalid, and if the drag proxy will animate
120912      * back to the point from which the drag began.
120913      *
120914      * Returning `0` To this event signals that the data transfer operation should not take place, but that the gesture
120915      * was valid, and that the repair operation should not take place.
120916      *
120917      * Any other return value continues with the data transfer operation.
120918      *
120919      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone
120920      * DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:
120921      *
120922      * - copy : Boolean
120923      *
120924      *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` and
120925      *   the control key was pressed when the drag operation was begun
120926      *
120927      * - view : GridView
120928      *
120929      *   The source GridView from which the drag originated.
120930      *
120931      * - ddel : HtmlElement
120932      *
120933      *   The drag proxy element which moves with the mouse
120934      *
120935      * - item : HtmlElement
120936      *
120937      *   The GridView node upon which the mousedown event was registered.
120938      *
120939      * - records : Array
120940      *
120941      *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
120942      *
120943      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
120944      *
120945      * @param {String} dropPosition `"before"` or `"after"` depending on whether the mouse is above or below the midline
120946      * of the node.
120947      *
120948      * @param {Function} dropFunction
120949      *
120950      * A function to call to complete the data transfer operation and either move or copy Model instances from the
120951      * source View's Store to the destination View's Store.
120952      *
120953      * This is useful when you want to perform some kind of asynchronous processing before confirming the drop, such as
120954      * an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.
120955      *
120956      * Return `0` from this event handler, and call the `dropFunction` at any time to perform the data transfer.
120957      */
120958
120959     /**
120960      * @event drop
120961      * **This event is fired through the GridView. Add listeners to the GridView object** Fired when a drop operation
120962      * has been completed and the data has been moved or copied.
120963      *
120964      * @param {HTMLElement} node The GridView node **if any** over which the mouse was positioned.
120965      *
120966      * @param {Object} data The data object gathered at mousedown time by the cooperating {@link Ext.dd.DragZone
120967      * DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following properties:
120968      *
120969      * - copy : Boolean
120970      *
120971      *   The value of the GridView's `copy` property, or `true` if the GridView was configured with `allowCopy: true` and
120972      *   the control key was pressed when the drag operation was begun
120973      *
120974      * - view : GridView
120975      *
120976      *   The source GridView from which the drag originated.
120977      *
120978      * - ddel : HtmlElement
120979      *
120980      *   The drag proxy element which moves with the mouse
120981      *
120982      * - item : HtmlElement
120983      *
120984      *   The GridView node upon which the mousedown event was registered.
120985      *
120986      * - records : Array
120987      *
120988      *   An Array of {@link Ext.data.Model Model}s representing the selected data being dragged from the source GridView.
120989      *
120990      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
120991      *
120992      * @param {String} dropPosition `"before"` or `"after"` depending on whether the mouse is above or below the midline
120993      * of the node.
120994      */
120995
120996     dragText : '{0} selected row{1}',
120997
120998     /**
120999      * @cfg {String} ddGroup
121000      * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
121001      * DropZone used by this plugin will only interact with other drag drop objects in the same group.
121002      */
121003     ddGroup : "GridDD",
121004
121005     /**
121006      * @cfg {String} dragGroup
121007      * The ddGroup to which the DragZone will belong.
121008      *
121009      * This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other
121010      * Drag/DropZones which are members of the same ddGroup.
121011      */
121012
121013     /**
121014      * @cfg {String} dropGroup
121015      * The ddGroup to which the DropZone will belong.
121016      *
121017      * This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other
121018      * Drag/DropZones which are members of the same ddGroup.
121019      */
121020
121021     /**
121022      * @cfg {Boolean} enableDrop
121023      * False to disallow the View from accepting drop gestures.
121024      */
121025     enableDrop: true,
121026
121027     /**
121028      * @cfg {Boolean} enableDrag
121029      * False to disallow dragging items from the View.
121030      */
121031     enableDrag: true,
121032
121033     init : function(view) {
121034         view.on('render', this.onViewRender, this, {single: true});
121035     },
121036
121037     /**
121038      * @private
121039      * AbstractComponent calls destroy on all its plugins at destroy time.
121040      */
121041     destroy: function() {
121042         Ext.destroy(this.dragZone, this.dropZone);
121043     },
121044
121045     enable: function() {
121046         var me = this;
121047         if (me.dragZone) {
121048             me.dragZone.unlock();
121049         }
121050         if (me.dropZone) {
121051             me.dropZone.unlock();
121052         }
121053         me.callParent();
121054     },
121055
121056     disable: function() {
121057         var me = this;
121058         if (me.dragZone) {
121059             me.dragZone.lock();
121060         }
121061         if (me.dropZone) {
121062             me.dropZone.lock();
121063         }
121064         me.callParent();
121065     },
121066
121067     onViewRender : function(view) {
121068         var me = this;
121069
121070         if (me.enableDrag) {
121071             me.dragZone = Ext.create('Ext.view.DragZone', {
121072                 view: view,
121073                 ddGroup: me.dragGroup || me.ddGroup,
121074                 dragText: me.dragText
121075             });
121076         }
121077
121078         if (me.enableDrop) {
121079             me.dropZone = Ext.create('Ext.grid.ViewDropZone', {
121080                 view: view,
121081                 ddGroup: me.dropGroup || me.ddGroup
121082             });
121083         }
121084     }
121085 });
121086 /**
121087  * @class Ext.grid.plugin.HeaderReorderer
121088  * @extends Ext.util.Observable
121089  * @private
121090  */
121091 Ext.define('Ext.grid.plugin.HeaderReorderer', {
121092     extend: 'Ext.util.Observable',
121093     requires: ['Ext.grid.header.DragZone', 'Ext.grid.header.DropZone'],
121094     alias: 'plugin.gridheaderreorderer',
121095
121096     init: function(headerCt) {
121097         this.headerCt = headerCt;
121098         headerCt.on('render', this.onHeaderCtRender, this);
121099     },
121100
121101     /**
121102      * @private
121103      * AbstractComponent calls destroy on all its plugins at destroy time.
121104      */
121105     destroy: function() {
121106         Ext.destroy(this.dragZone, this.dropZone);
121107     },
121108
121109     onHeaderCtRender: function() {
121110         this.dragZone = Ext.create('Ext.grid.header.DragZone', this.headerCt);
121111         this.dropZone = Ext.create('Ext.grid.header.DropZone', this.headerCt);
121112         if (this.disabled) {
121113             this.dragZone.disable();
121114         }
121115     },
121116     
121117     enable: function() {
121118         this.disabled = false;
121119         if (this.dragZone) {
121120             this.dragZone.enable();
121121         }
121122     },
121123     
121124     disable: function() {
121125         this.disabled = true;
121126         if (this.dragZone) {
121127             this.dragZone.disable();
121128         }
121129     }
121130 });
121131 /**
121132  * @class Ext.grid.plugin.HeaderResizer
121133  * @extends Ext.util.Observable
121134  *
121135  * Plugin to add header resizing functionality to a HeaderContainer.
121136  * Always resizing header to the left of the splitter you are resizing.
121137  */
121138 Ext.define('Ext.grid.plugin.HeaderResizer', {
121139     extend: 'Ext.util.Observable',
121140     requires: ['Ext.dd.DragTracker', 'Ext.util.Region'],
121141     alias: 'plugin.gridheaderresizer',
121142
121143     disabled: false,
121144
121145     /**
121146      * @cfg {Boolean} dynamic
121147      * Set to true to resize on the fly rather than using a proxy marker. Defaults to false.
121148      */
121149     configs: {
121150         dynamic: true
121151     },
121152
121153     colHeaderCls: Ext.baseCSSPrefix + 'column-header',
121154
121155     minColWidth: 40,
121156     maxColWidth: 1000,
121157     wResizeCursor: 'col-resize',
121158     eResizeCursor: 'col-resize',
121159     // not using w and e resize bc we are only ever resizing one
121160     // column
121161     //wResizeCursor: Ext.isWebKit ? 'w-resize' : 'col-resize',
121162     //eResizeCursor: Ext.isWebKit ? 'e-resize' : 'col-resize',
121163
121164     init: function(headerCt) {
121165         this.headerCt = headerCt;
121166         headerCt.on('render', this.afterHeaderRender, this, {single: true});
121167     },
121168
121169     /**
121170      * @private
121171      * AbstractComponent calls destroy on all its plugins at destroy time.
121172      */
121173     destroy: function() {
121174         if (this.tracker) {
121175             this.tracker.destroy();
121176         }
121177     },
121178
121179     afterHeaderRender: function() {
121180         var headerCt = this.headerCt,
121181             el = headerCt.el;
121182
121183         headerCt.mon(el, 'mousemove', this.onHeaderCtMouseMove, this);
121184
121185         this.tracker = Ext.create('Ext.dd.DragTracker', {
121186             disabled: this.disabled,
121187             onBeforeStart: Ext.Function.bind(this.onBeforeStart, this),
121188             onStart: Ext.Function.bind(this.onStart, this),
121189             onDrag: Ext.Function.bind(this.onDrag, this),
121190             onEnd: Ext.Function.bind(this.onEnd, this),
121191             tolerance: 3,
121192             autoStart: 300,
121193             el: el
121194         });
121195     },
121196
121197     // As we mouse over individual headers, change the cursor to indicate
121198     // that resizing is available, and cache the resize target header for use
121199     // if/when they mousedown.
121200     onHeaderCtMouseMove: function(e, t) {
121201         if (this.headerCt.dragging) {
121202             if (this.activeHd) {
121203                 this.activeHd.el.dom.style.cursor = '';
121204                 delete this.activeHd;
121205             }
121206         } else {
121207             var headerEl = e.getTarget('.' + this.colHeaderCls, 3, true),
121208                 overHeader, resizeHeader;
121209
121210             if (headerEl){
121211                 overHeader = Ext.getCmp(headerEl.id);
121212
121213                 // On left edge, go back to the previous non-hidden header.
121214                 if (overHeader.isOnLeftEdge(e)) {
121215                     resizeHeader = overHeader.previousNode('gridcolumn:not([hidden])');
121216
121217                 }
121218                 // Else, if on the right edge, we're resizing the column we are over
121219                 else if (overHeader.isOnRightEdge(e)) {
121220                     resizeHeader = overHeader;
121221                 }
121222                 // Between the edges: we are not resizing
121223                 else {
121224                     resizeHeader = null;
121225                 }
121226
121227                 // We *are* resizing
121228                 if (resizeHeader) {
121229                     // If we're attempting to resize a group header, that cannot be resized,
121230                     // so find its last visible leaf header; Group headers are sized
121231                     // by the size of their child headers.
121232                     if (resizeHeader.isGroupHeader) {
121233                         resizeHeader = resizeHeader.down(':not([isGroupHeader]):not([hidden]):last');
121234                     }
121235
121236                     // Check if the header is resizable. Continue checking the old "fixed" property, bug also
121237                     // check whether the resizablwe property is set to false.
121238                     if (resizeHeader && !(resizeHeader.fixed || (resizeHeader.resizable === false) || this.disabled)) {
121239                         this.activeHd = resizeHeader;
121240                         overHeader.el.dom.style.cursor = this.eResizeCursor;
121241                     }
121242                 // reset
121243                 } else {
121244                     overHeader.el.dom.style.cursor = '';
121245                     delete this.activeHd;
121246                 }
121247             }
121248         }
121249     },
121250
121251     // only start when there is an activeHd
121252     onBeforeStart : function(e){
121253         var t = e.getTarget();
121254         // cache the activeHd because it will be cleared.
121255         this.dragHd = this.activeHd;
121256
121257         if (!!this.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger') && !this.headerCt.dragging) {
121258             //this.headerCt.dragging = true;
121259             this.tracker.constrainTo = this.getConstrainRegion();
121260             return true;
121261         } else {
121262             this.headerCt.dragging = false;
121263             return false;
121264         }
121265     },
121266
121267     // get the region to constrain to, takes into account max and min col widths
121268     getConstrainRegion: function() {
121269         var dragHdEl = this.dragHd.el,
121270             region   = Ext.util.Region.getRegion(dragHdEl);
121271
121272         return region.adjust(
121273             0,
121274             this.maxColWidth - dragHdEl.getWidth(),
121275             0,
121276             this.minColWidth
121277         );
121278     },
121279
121280     // initialize the left and right hand side markers around
121281     // the header that we are resizing
121282     onStart: function(e){
121283         var me       = this,
121284             dragHd   = me.dragHd,
121285             dragHdEl = dragHd.el,
121286             width    = dragHdEl.getWidth(),
121287             headerCt = me.headerCt,
121288             t        = e.getTarget();
121289
121290         if (me.dragHd && !Ext.fly(t).hasCls('x-column-header-trigger')) {
121291             headerCt.dragging = true;
121292         }
121293
121294         me.origWidth = width;
121295
121296         // setup marker proxies
121297         if (!me.dynamic) {
121298             var xy           = dragHdEl.getXY(),
121299                 gridSection  = headerCt.up('[scrollerOwner]'),
121300                 dragHct      = me.dragHd.up(':not([isGroupHeader])'),
121301                 firstSection = dragHct.up(),
121302                 lhsMarker    = gridSection.getLhsMarker(),
121303                 rhsMarker    = gridSection.getRhsMarker(),
121304                 el           = rhsMarker.parent(),
121305                 offsetLeft   = el.getLeft(true),
121306                 offsetTop    = el.getTop(true),
121307                 topLeft      = el.translatePoints(xy),
121308                 markerHeight = firstSection.body.getHeight() + headerCt.getHeight(),
121309                 top = topLeft.top - offsetTop;
121310
121311             lhsMarker.setTop(top);
121312             rhsMarker.setTop(top);
121313             lhsMarker.setHeight(markerHeight);
121314             rhsMarker.setHeight(markerHeight);
121315             lhsMarker.setLeft(topLeft.left - offsetLeft);
121316             rhsMarker.setLeft(topLeft.left + width - offsetLeft);
121317         }
121318     },
121319
121320     // synchronize the rhsMarker with the mouse movement
121321     onDrag: function(e){
121322         if (!this.dynamic) {
121323             var xy          = this.tracker.getXY('point'),
121324                 gridSection = this.headerCt.up('[scrollerOwner]'),
121325                 rhsMarker   = gridSection.getRhsMarker(),
121326                 el          = rhsMarker.parent(),
121327                 topLeft     = el.translatePoints(xy),
121328                 offsetLeft  = el.getLeft(true);
121329
121330             rhsMarker.setLeft(topLeft.left - offsetLeft);
121331         // Resize as user interacts
121332         } else {
121333             this.doResize();
121334         }
121335     },
121336
121337     onEnd: function(e){
121338         this.headerCt.dragging = false;
121339         if (this.dragHd) {
121340             if (!this.dynamic) {
121341                 var dragHd      = this.dragHd,
121342                     gridSection = this.headerCt.up('[scrollerOwner]'),
121343                     lhsMarker   = gridSection.getLhsMarker(),
121344                     rhsMarker   = gridSection.getRhsMarker(),
121345                     currWidth   = dragHd.getWidth(),
121346                     offset      = this.tracker.getOffset('point'),
121347                     offscreen   = -9999;
121348
121349                 // hide markers
121350                 lhsMarker.setLeft(offscreen);
121351                 rhsMarker.setLeft(offscreen);
121352             }
121353             this.doResize();
121354         }
121355     },
121356
121357     doResize: function() {
121358         if (this.dragHd) {
121359             var dragHd = this.dragHd,
121360                 nextHd,
121361                 offset = this.tracker.getOffset('point');
121362
121363             // resize the dragHd
121364             if (dragHd.flex) {
121365                 delete dragHd.flex;
121366             }
121367
121368             this.headerCt.suspendLayout = true;
121369             dragHd.setWidth(this.origWidth + offset[0], false);
121370
121371             // In the case of forceFit, change the following Header width.
121372             // Then apply the two width changes by laying out the owning HeaderContainer
121373             // If HeaderContainer is configured forceFit, inhibit upstream layout notification, so that
121374             // we can also shrink the following Header by an equal amount, and *then* inform the upstream layout.
121375             if (this.headerCt.forceFit) {
121376                 nextHd = dragHd.nextNode('gridcolumn:not([hidden]):not([isGroupHeader])');
121377                 if (nextHd) {
121378                     delete nextHd.flex;
121379                     nextHd.setWidth(nextHd.getWidth() - offset[0], false);
121380                 }
121381             }
121382             this.headerCt.suspendLayout = false;
121383             this.headerCt.doComponentLayout(this.headerCt.getFullWidth());
121384         }
121385     },
121386
121387     disable: function() {
121388         this.disabled = true;
121389         if (this.tracker) {
121390             this.tracker.disable();
121391         }
121392     },
121393
121394     enable: function() {
121395         this.disabled = false;
121396         if (this.tracker) {
121397             this.tracker.enable();
121398         }
121399     }
121400 });
121401 /**
121402  * The Ext.grid.plugin.RowEditing plugin injects editing at a row level for a Grid. When editing begins,
121403  * a small floating dialog will be shown for the appropriate row. Each editable column will show a field
121404  * for editing. There is a button to save or cancel all changes for the edit.
121405  *
121406  * The field that will be used for the editor is defined at the
121407  * {@link Ext.grid.column.Column#editor editor}. The editor can be a field instance or a field configuration.
121408  * If an editor is not specified for a particular column then that column won't be editable and the value of
121409  * the column will be displayed.
121410  *
121411  * The editor may be shared for each column in the grid, or a different one may be specified for each column.
121412  * An appropriate field type should be chosen to match the data structure that it will be editing. For example,
121413  * to edit a date, it would be useful to specify {@link Ext.form.field.Date} as the editor.
121414  *
121415  *     @example
121416  *     Ext.create('Ext.data.Store', {
121417  *         storeId:'simpsonsStore',
121418  *         fields:['name', 'email', 'phone'],
121419  *         data: [
121420  *             {"name":"Lisa", "email":"lisa@simpsons.com", "phone":"555-111-1224"},
121421  *             {"name":"Bart", "email":"bart@simpsons.com", "phone":"555--222-1234"},
121422  *             {"name":"Homer", "email":"home@simpsons.com", "phone":"555-222-1244"},
121423  *             {"name":"Marge", "email":"marge@simpsons.com", "phone":"555-222-1254"}
121424  *         ]
121425  *     });
121426  *
121427  *     Ext.create('Ext.grid.Panel', {
121428  *         title: 'Simpsons',
121429  *         store: Ext.data.StoreManager.lookup('simpsonsStore'),
121430  *         columns: [
121431  *             {header: 'Name',  dataIndex: 'name', editor: 'textfield'},
121432  *             {header: 'Email', dataIndex: 'email', flex:1,
121433  *                 editor: {
121434  *                     xtype: 'textfield',
121435  *                     allowBlank: false
121436  *                 }
121437  *             },
121438  *             {header: 'Phone', dataIndex: 'phone'}
121439  *         ],
121440  *         selType: 'rowmodel',
121441  *         plugins: [
121442  *             Ext.create('Ext.grid.plugin.RowEditing', {
121443  *                 clicksToEdit: 1
121444  *             })
121445  *         ],
121446  *         height: 200,
121447  *         width: 400,
121448  *         renderTo: Ext.getBody()
121449  *     });
121450  */
121451 Ext.define('Ext.grid.plugin.RowEditing', {
121452     extend: 'Ext.grid.plugin.Editing',
121453     alias: 'plugin.rowediting',
121454
121455     requires: [
121456         'Ext.grid.RowEditor'
121457     ],
121458
121459     editStyle: 'row',
121460
121461     /**
121462      * @cfg {Boolean} autoCancel
121463      * True to automatically cancel any pending changes when the row editor begins editing a new row.
121464      * False to force the user to explicitly cancel the pending changes. Defaults to true.
121465      */
121466     autoCancel: true,
121467
121468     /**
121469      * @cfg {Number} clicksToMoveEditor
121470      * The number of clicks to move the row editor to a new row while it is visible and actively editing another row.
121471      * This will default to the same value as {@link Ext.grid.plugin.Editing#clicksToEdit clicksToEdit}.
121472      */
121473
121474     /**
121475      * @cfg {Boolean} errorSummary
121476      * True to show a {@link Ext.tip.ToolTip tooltip} that summarizes all validation errors present
121477      * in the row editor. Set to false to prevent the tooltip from showing. Defaults to true.
121478      */
121479     errorSummary: true,
121480
121481     /**
121482      * @event beforeedit
121483      * Fires before row editing is triggered.
121484      *
121485      * @param {Ext.grid.plugin.Editing} editor
121486      * @param {Object} e An edit event with the following properties:
121487      *
121488      * - grid - The grid this editor is on
121489      * - view - The grid view
121490      * - store - The grid store
121491      * - record - The record being edited
121492      * - row - The grid table row
121493      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
121494      * - rowIdx - The row index that is being edited
121495      * - colIdx - The column index that initiated the edit
121496      * - cancel - Set this to true to cancel the edit or return false from your handler.
121497      */
121498     
121499     /**
121500      * @event canceledit
121501      * Fires when the user has started editing a row but then cancelled the edit
121502      * @param {Object} grid The grid
121503      */
121504     
121505     /**
121506      * @event edit
121507      * Fires after a row is edited. Usage example:
121508      *
121509      *     grid.on('edit', function(editor, e) {
121510      *         // commit the changes right after editing finished
121511      *         e.record.commit();
121512      *     };
121513      *
121514      * @param {Ext.grid.plugin.Editing} editor
121515      * @param {Object} e An edit event with the following properties:
121516      *
121517      * - grid - The grid this editor is on
121518      * - view - The grid view
121519      * - store - The grid store
121520      * - record - The record being edited
121521      * - row - The grid table row
121522      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
121523      * - rowIdx - The row index that is being edited
121524      * - colIdx - The column index that initiated the edit
121525      */
121526     /**
121527      * @event validateedit
121528      * Fires after a cell is edited, but before the value is set in the record. Return false to cancel the change. The
121529      * edit event object has the following properties
121530      *
121531      * Usage example showing how to remove the red triangle (dirty record indicator) from some records (not all). By
121532      * observing the grid's validateedit event, it can be cancelled if the edit occurs on a targeted row (for example)
121533      * and then setting the field's new value in the Record directly:
121534      *
121535      *     grid.on('validateedit', function(editor, e) {
121536      *       var myTargetRow = 6;
121537      *
121538      *       if (e.rowIdx == myTargetRow) {
121539      *         e.cancel = true;
121540      *         e.record.data[e.field] = e.value;
121541      *       }
121542      *     });
121543      *
121544      * @param {Ext.grid.plugin.Editing} editor
121545      * @param {Object} e An edit event with the following properties:
121546      *
121547      * - grid - The grid this editor is on
121548      * - view - The grid view
121549      * - store - The grid store
121550      * - record - The record being edited
121551      * - row - The grid table row
121552      * - column - The grid {@link Ext.grid.column.Column Column} defining the column that initiated the edit
121553      * - rowIdx - The row index that is being edited
121554      * - colIdx - The column index that initiated the edit
121555      * - cancel - Set this to true to cancel the edit or return false from your handler.
121556      */
121557
121558     constructor: function() {
121559         var me = this;
121560         me.callParent(arguments);
121561
121562         if (!me.clicksToMoveEditor) {
121563             me.clicksToMoveEditor = me.clicksToEdit;
121564         }
121565
121566         me.autoCancel = !!me.autoCancel;
121567     },
121568
121569     /**
121570      * @private
121571      * AbstractComponent calls destroy on all its plugins at destroy time.
121572      */
121573     destroy: function() {
121574         var me = this;
121575         Ext.destroy(me.editor);
121576         me.callParent(arguments);
121577     },
121578
121579     /**
121580      * Starts editing the specified record, using the specified Column definition to define which field is being edited.
121581      * @param {Ext.data.Model} record The Store data record which backs the row to be edited.
121582      * @param {Ext.data.Model} columnHeader The Column object defining the column to be edited. @override
121583      */
121584     startEdit: function(record, columnHeader) {
121585         var me = this,
121586             editor = me.getEditor();
121587
121588         if (me.callParent(arguments) === false) {
121589             return false;
121590         }
121591
121592         // Fire off our editor
121593         if (editor.beforeEdit() !== false) {
121594             editor.startEdit(me.context.record, me.context.column);
121595         }
121596     },
121597
121598     // private
121599     cancelEdit: function() {
121600         var me = this;
121601
121602         if (me.editing) {
121603             me.getEditor().cancelEdit();
121604             me.callParent(arguments);
121605             
121606             me.fireEvent('canceledit', me.context);
121607         }
121608     },
121609
121610     // private
121611     completeEdit: function() {
121612         var me = this;
121613
121614         if (me.editing && me.validateEdit()) {
121615             me.editing = false;
121616             me.fireEvent('edit', me.context);
121617         }
121618     },
121619
121620     // private
121621     validateEdit: function() {
121622         var me             = this,
121623             editor         = me.editor,
121624             context        = me.context,
121625             record         = context.record,
121626             newValues      = {},
121627             originalValues = {},
121628             name;
121629
121630         editor.items.each(function(item) {
121631             name = item.name;
121632
121633             newValues[name]      = item.getValue();
121634             originalValues[name] = record.get(name);
121635         });
121636
121637         Ext.apply(context, {
121638             newValues      : newValues,
121639             originalValues : originalValues
121640         });
121641
121642         return me.callParent(arguments) && me.getEditor().completeEdit();
121643     },
121644
121645     // private
121646     getEditor: function() {
121647         var me = this;
121648
121649         if (!me.editor) {
121650             me.editor = me.initEditor();
121651         }
121652         return me.editor;
121653     },
121654
121655     // private
121656     initEditor: function() {
121657         var me = this,
121658             grid = me.grid,
121659             view = me.view,
121660             headerCt = grid.headerCt;
121661
121662         return Ext.create('Ext.grid.RowEditor', {
121663             autoCancel: me.autoCancel,
121664             errorSummary: me.errorSummary,
121665             fields: headerCt.getGridColumns(),
121666             hidden: true,
121667
121668             // keep a reference..
121669             editingPlugin: me,
121670             renderTo: view.el
121671         });
121672     },
121673
121674     // private
121675     initEditTriggers: function() {
121676         var me = this,
121677             grid = me.grid,
121678             view = me.view,
121679             headerCt = grid.headerCt,
121680             moveEditorEvent = me.clicksToMoveEditor === 1 ? 'click' : 'dblclick';
121681
121682         me.callParent(arguments);
121683
121684         if (me.clicksToMoveEditor !== me.clicksToEdit) {
121685             me.mon(view, 'cell' + moveEditorEvent, me.moveEditorByClick, me);
121686         }
121687
121688         view.on('render', function() {
121689             // Column events
121690             me.mon(headerCt, {
121691                 add: me.onColumnAdd,
121692                 remove: me.onColumnRemove,
121693                 columnresize: me.onColumnResize,
121694                 columnhide: me.onColumnHide,
121695                 columnshow: me.onColumnShow,
121696                 columnmove: me.onColumnMove,
121697                 scope: me
121698             });
121699         }, me, { single: true });
121700     },
121701
121702     startEditByClick: function() {
121703         var me = this;
121704         if (!me.editing || me.clicksToMoveEditor === me.clicksToEdit) {
121705             me.callParent(arguments);
121706         }
121707     },
121708
121709     moveEditorByClick: function() {
121710         var me = this;
121711         if (me.editing) {
121712             me.superclass.startEditByClick.apply(me, arguments);
121713         }
121714     },
121715
121716     // private
121717     onColumnAdd: function(ct, column) {
121718         if (column.isHeader) {
121719             var me = this,
121720                 editor;
121721
121722             me.initFieldAccessors(column);
121723             editor = me.getEditor();
121724
121725             if (editor && editor.onColumnAdd) {
121726                 editor.onColumnAdd(column);
121727             }
121728         }
121729     },
121730
121731     // private
121732     onColumnRemove: function(ct, column) {
121733         if (column.isHeader) {
121734             var me = this,
121735                 editor = me.getEditor();
121736
121737             if (editor && editor.onColumnRemove) {
121738                 editor.onColumnRemove(column);
121739             }
121740             me.removeFieldAccessors(column);
121741         }
121742     },
121743
121744     // private
121745     onColumnResize: function(ct, column, width) {
121746         if (column.isHeader) {
121747             var me = this,
121748                 editor = me.getEditor();
121749
121750             if (editor && editor.onColumnResize) {
121751                 editor.onColumnResize(column, width);
121752             }
121753         }
121754     },
121755
121756     // private
121757     onColumnHide: function(ct, column) {
121758         // no isHeader check here since its already a columnhide event.
121759         var me = this,
121760             editor = me.getEditor();
121761
121762         if (editor && editor.onColumnHide) {
121763             editor.onColumnHide(column);
121764         }
121765     },
121766
121767     // private
121768     onColumnShow: function(ct, column) {
121769         // no isHeader check here since its already a columnshow event.
121770         var me = this,
121771             editor = me.getEditor();
121772
121773         if (editor && editor.onColumnShow) {
121774             editor.onColumnShow(column);
121775         }
121776     },
121777
121778     // private
121779     onColumnMove: function(ct, column, fromIdx, toIdx) {
121780         // no isHeader check here since its already a columnmove event.
121781         var me = this,
121782             editor = me.getEditor();
121783
121784         if (editor && editor.onColumnMove) {
121785             editor.onColumnMove(column, fromIdx, toIdx);
121786         }
121787     },
121788
121789     // private
121790     setColumnField: function(column, field) {
121791         var me = this;
121792         me.callParent(arguments);
121793         me.getEditor().setField(column.field, column);
121794     }
121795 });
121796
121797 /**
121798  * @class Ext.grid.property.Grid
121799  * @extends Ext.grid.Panel
121800  *
121801  * A specialized grid implementation intended to mimic the traditional property grid as typically seen in
121802  * development IDEs.  Each row in the grid represents a property of some object, and the data is stored
121803  * as a set of name/value pairs in {@link Ext.grid.property.Property Properties}.  Example usage:
121804  *
121805  *     @example
121806  *     Ext.create('Ext.grid.property.Grid', {
121807  *         title: 'Properties Grid',
121808  *         width: 300,
121809  *         renderTo: Ext.getBody(),
121810  *         source: {
121811  *             "(name)": "My Object",
121812  *             "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),
121813  *             "Available": false,
121814  *             "Version": .01,
121815  *             "Description": "A test object"
121816  *         }
121817  *     });
121818  */
121819 Ext.define('Ext.grid.property.Grid', {
121820
121821     extend: 'Ext.grid.Panel',
121822
121823     alias: 'widget.propertygrid',
121824
121825     alternateClassName: 'Ext.grid.PropertyGrid',
121826
121827     uses: [
121828        'Ext.grid.plugin.CellEditing',
121829        'Ext.grid.property.Store',
121830        'Ext.grid.property.HeaderContainer',
121831        'Ext.XTemplate',
121832        'Ext.grid.CellEditor',
121833        'Ext.form.field.Date',
121834        'Ext.form.field.Text',
121835        'Ext.form.field.Number'
121836     ],
121837
121838    /**
121839     * @cfg {Object} propertyNames An object containing custom property name/display name pairs.
121840     * If specified, the display name will be shown in the name column instead of the property name.
121841     */
121842
121843     /**
121844     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
121845     */
121846
121847     /**
121848     * @cfg {Object} customEditors An object containing name/value pairs of custom editor type definitions that allow
121849     * the grid to support additional types of editable fields.  By default, the grid supports strongly-typed editing
121850     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
121851     * associated with a custom input control by specifying a custom editor.  The name of the editor
121852     * type should correspond with the name of the property that will use the editor.  Example usage:
121853     * <pre><code>
121854 var grid = new Ext.grid.property.Grid({
121855
121856     // Custom editors for certain property names
121857     customEditors: {
121858         evtStart: Ext.create('Ext.form.TimeField' {selectOnFocus:true})
121859     },
121860
121861     // Displayed name for property names in the source
121862     propertyNames: {
121863         evtStart: 'Start Time'
121864     },
121865
121866     // Data object containing properties to edit
121867     source: {
121868         evtStart: '10:00 AM'
121869     }
121870 });
121871 </code></pre>
121872     */
121873
121874     /**
121875     * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
121876     */
121877
121878     /**
121879     * @cfg {Object} customRenderers An object containing name/value pairs of custom renderer type definitions that allow
121880     * the grid to support custom rendering of fields.  By default, the grid supports strongly-typed rendering
121881     * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
121882     * associated with the type of the value.  The name of the renderer type should correspond with the name of the property
121883     * that it will render.  Example usage:
121884     * <pre><code>
121885 var grid = Ext.create('Ext.grid.property.Grid', {
121886     customRenderers: {
121887         Available: function(v){
121888             if (v) {
121889                 return '<span style="color: green;">Yes</span>';
121890             } else {
121891                 return '<span style="color: red;">No</span>';
121892             }
121893         }
121894     },
121895     source: {
121896         Available: true
121897     }
121898 });
121899 </code></pre>
121900     */
121901
121902     /**
121903      * @cfg {String} valueField
121904      * Optional. The name of the field from the property store to use as the value field name. Defaults to <code>'value'</code>
121905      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
121906      */
121907     valueField: 'value',
121908
121909     /**
121910      * @cfg {String} nameField
121911      * Optional. The name of the field from the property store to use as the property field name. Defaults to <code>'name'</code>
121912      * This may be useful if you do not configure the property Grid from an object, but use your own store configuration.
121913      */
121914     nameField: 'name',
121915
121916     /**
121917      * @cfg {Number} nameColumnWidth
121918      * Optional. Specify the width for the name column. The value column will take any remaining space. Defaults to <tt>115</tt>.
121919      */
121920
121921     // private config overrides
121922     enableColumnMove: false,
121923     columnLines: true,
121924     stripeRows: false,
121925     trackMouseOver: false,
121926     clicksToEdit: 1,
121927     enableHdMenu: false,
121928
121929     // private
121930     initComponent : function(){
121931         var me = this;
121932
121933         me.addCls(Ext.baseCSSPrefix + 'property-grid');
121934         me.plugins = me.plugins || [];
121935
121936         // Enable cell editing. Inject a custom startEdit which always edits column 1 regardless of which column was clicked.
121937         me.plugins.push(Ext.create('Ext.grid.plugin.CellEditing', {
121938             clicksToEdit: me.clicksToEdit,
121939
121940             // Inject a startEdit which always edits the value column
121941             startEdit: function(record, column) {
121942                 // Maintainer: Do not change this 'this' to 'me'! It is the CellEditing object's own scope.
121943                 return this.self.prototype.startEdit.call(this, record, me.headerCt.child('#' + me.valueField));
121944             }
121945         }));
121946
121947         me.selModel = {
121948             selType: 'cellmodel',
121949             onCellSelect: function(position) {
121950                 if (position.column != 1) {
121951                     position.column = 1;
121952                 }
121953                 return this.self.prototype.onCellSelect.call(this, position);
121954             }
121955         };
121956         me.customRenderers = me.customRenderers || {};
121957         me.customEditors = me.customEditors || {};
121958
121959         // Create a property.Store from the source object unless configured with a store
121960         if (!me.store) {
121961             me.propStore = me.store = Ext.create('Ext.grid.property.Store', me, me.source);
121962         }
121963
121964         me.store.sort('name', 'ASC');
121965         me.columns = Ext.create('Ext.grid.property.HeaderContainer', me, me.store);
121966
121967         me.addEvents(
121968             /**
121969              * @event beforepropertychange
121970              * Fires before a property value changes.  Handlers can return false to cancel the property change
121971              * (this will internally call {@link Ext.data.Model#reject} on the property's record).
121972              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
121973              * as the {@link #source} config property).
121974              * @param {String} recordId The record's id in the data store
121975              * @param {Object} value The current edited property value
121976              * @param {Object} oldValue The original property value prior to editing
121977              */
121978             'beforepropertychange',
121979             /**
121980              * @event propertychange
121981              * Fires after a property value has changed.
121982              * @param {Object} source The source data object for the grid (corresponds to the same object passed in
121983              * as the {@link #source} config property).
121984              * @param {String} recordId The record's id in the data store
121985              * @param {Object} value The current edited property value
121986              * @param {Object} oldValue The original property value prior to editing
121987              */
121988             'propertychange'
121989         );
121990         me.callParent();
121991
121992         // Inject a custom implementation of walkCells which only goes up or down
121993         me.getView().walkCells = this.walkCells;
121994
121995         // Set up our default editor set for the 4 atomic data types
121996         me.editors = {
121997             'date'    : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Date',   {selectOnFocus: true})}),
121998             'string'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Text',   {selectOnFocus: true})}),
121999             'number'  : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.Number', {selectOnFocus: true})}),
122000             'boolean' : Ext.create('Ext.grid.CellEditor', { field: Ext.create('Ext.form.field.ComboBox', {
122001                 editable: false,
122002                 store: [[ true, me.headerCt.trueText ], [false, me.headerCt.falseText ]]
122003             })})
122004         };
122005
122006         // Track changes to the data so we can fire our events.
122007         me.store.on('update', me.onUpdate, me);
122008     },
122009
122010     // private
122011     onUpdate : function(store, record, operation) {
122012         var me = this,
122013             v, oldValue;
122014
122015         if (operation == Ext.data.Model.EDIT) {
122016             v = record.get(me.valueField);
122017             oldValue = record.modified.value;
122018             if (me.fireEvent('beforepropertychange', me.source, record.getId(), v, oldValue) !== false) {
122019                 if (me.source) {
122020                     me.source[record.getId()] = v;
122021                 }
122022                 record.commit();
122023                 me.fireEvent('propertychange', me.source, record.getId(), v, oldValue);
122024             } else {
122025                 record.reject();
122026             }
122027         }
122028     },
122029
122030     // Custom implementation of walkCells which only goes up and down.
122031     walkCells: function(pos, direction, e, preventWrap, verifierFn, scope) {
122032         if (direction == 'left') {
122033             direction = 'up';
122034         } else if (direction == 'right') {
122035             direction = 'down';
122036         }
122037         pos = Ext.view.Table.prototype.walkCells.call(this, pos, direction, e, preventWrap, verifierFn, scope);
122038         if (!pos.column) {
122039             pos.column = 1;
122040         }
122041         return pos;
122042     },
122043
122044     // private
122045     // returns the correct editor type for the property type, or a custom one keyed by the property name
122046     getCellEditor : function(record, column) {
122047         var me = this,
122048             propName = record.get(me.nameField),
122049             val = record.get(me.valueField),
122050             editor = me.customEditors[propName];
122051
122052         // A custom editor was found. If not already wrapped with a CellEditor, wrap it, and stash it back
122053         // If it's not even a Field, just a config object, instantiate it before wrapping it.
122054         if (editor) {
122055             if (!(editor instanceof Ext.grid.CellEditor)) {
122056                 if (!(editor instanceof Ext.form.field.Base)) {
122057                     editor = Ext.ComponentManager.create(editor, 'textfield');
122058                 }
122059                 editor = me.customEditors[propName] = Ext.create('Ext.grid.CellEditor', { field: editor });
122060             }
122061         } else if (Ext.isDate(val)) {
122062             editor = me.editors.date;
122063         } else if (Ext.isNumber(val)) {
122064             editor = me.editors.number;
122065         } else if (Ext.isBoolean(val)) {
122066             editor = me.editors['boolean'];
122067         } else {
122068             editor = me.editors.string;
122069         }
122070
122071         // Give the editor a unique ID because the CellEditing plugin caches them
122072         editor.editorId = propName;
122073         return editor;
122074     },
122075
122076     beforeDestroy: function() {
122077         var me = this;
122078         me.callParent();
122079         me.destroyEditors(me.editors);
122080         me.destroyEditors(me.customEditors);
122081         delete me.source;
122082     },
122083
122084     destroyEditors: function (editors) {
122085         for (var ed in editors) {
122086             if (editors.hasOwnProperty(ed)) {
122087                 Ext.destroy(editors[ed]);
122088             }
122089         }
122090     },
122091
122092     /**
122093      * Sets the source data object containing the property data.  The data object can contain one or more name/value
122094      * pairs representing all of the properties of an object to display in the grid, and this data will automatically
122095      * be loaded into the grid's {@link #store}.  The values should be supplied in the proper data type if needed,
122096      * otherwise string type will be assumed.  If the grid already contains data, this method will replace any
122097      * existing data.  See also the {@link #source} config value.  Example usage:
122098      * <pre><code>
122099 grid.setSource({
122100     "(name)": "My Object",
122101     "Created": Ext.Date.parse('10/15/2006', 'm/d/Y'),  // date type
122102     "Available": false,  // boolean type
122103     "Version": .01,      // decimal type
122104     "Description": "A test object"
122105 });
122106 </code></pre>
122107      * @param {Object} source The data object
122108      */
122109     setSource: function(source) {
122110         this.source = source;
122111         this.propStore.setSource(source);
122112     },
122113
122114     /**
122115      * Gets the source data object containing the property data.  See {@link #setSource} for details regarding the
122116      * format of the data object.
122117      * @return {Object} The data object
122118      */
122119     getSource: function() {
122120         return this.propStore.getSource();
122121     },
122122
122123     /**
122124      * Sets the value of a property.
122125      * @param {String} prop The name of the property to set
122126      * @param {Object} value The value to test
122127      * @param {Boolean} create (Optional) True to create the property if it doesn't already exist. Defaults to <tt>false</tt>.
122128      */
122129     setProperty: function(prop, value, create) {
122130         this.propStore.setValue(prop, value, create);
122131     },
122132
122133     /**
122134      * Removes a property from the grid.
122135      * @param {String} prop The name of the property to remove
122136      */
122137     removeProperty: function(prop) {
122138         this.propStore.remove(prop);
122139     }
122140
122141     /**
122142      * @cfg store
122143      * @hide
122144      */
122145     /**
122146      * @cfg colModel
122147      * @hide
122148      */
122149     /**
122150      * @cfg cm
122151      * @hide
122152      */
122153     /**
122154      * @cfg columns
122155      * @hide
122156      */
122157 });
122158 /**
122159  * @class Ext.grid.property.HeaderContainer
122160  * @extends Ext.grid.header.Container
122161  * A custom HeaderContainer for the {@link Ext.grid.property.Grid}.  Generally it should not need to be used directly.
122162  */
122163 Ext.define('Ext.grid.property.HeaderContainer', {
122164
122165     extend: 'Ext.grid.header.Container',
122166
122167     alternateClassName: 'Ext.grid.PropertyColumnModel',
122168     
122169     nameWidth: 115,
122170
122171     // private - strings used for locale support
122172     nameText : 'Name',
122173     valueText : 'Value',
122174     dateFormat : 'm/j/Y',
122175     trueText: 'true',
122176     falseText: 'false',
122177
122178     // private
122179     nameColumnCls: Ext.baseCSSPrefix + 'grid-property-name',
122180
122181     /**
122182      * Creates new HeaderContainer.
122183      * @param {Ext.grid.property.Grid} grid The grid this store will be bound to
122184      * @param {Object} source The source data config object
122185      */
122186     constructor : function(grid, store) {
122187         var me = this;
122188         
122189         me.grid = grid;
122190         me.store = store;
122191         me.callParent([{
122192             items: [{
122193                 header: me.nameText,
122194                 width: grid.nameColumnWidth || me.nameWidth,
122195                 sortable: true,
122196                 dataIndex: grid.nameField,
122197                 renderer: Ext.Function.bind(me.renderProp, me),
122198                 itemId: grid.nameField,
122199                 menuDisabled :true,
122200                 tdCls: me.nameColumnCls
122201             }, {
122202                 header: me.valueText,
122203                 renderer: Ext.Function.bind(me.renderCell, me),
122204                 getEditor: Ext.Function.bind(me.getCellEditor, me),
122205                 flex: 1,
122206                 fixed: true,
122207                 dataIndex: grid.valueField,
122208                 itemId: grid.valueField,
122209                 menuDisabled: true
122210             }]
122211         }]);
122212     },
122213     
122214     getCellEditor: function(record){
122215         return this.grid.getCellEditor(record, this);
122216     },
122217
122218     // private
122219     // Render a property name cell
122220     renderProp : function(v) {
122221         return this.getPropertyName(v);
122222     },
122223
122224     // private
122225     // Render a property value cell
122226     renderCell : function(val, meta, rec) {
122227         var me = this,
122228             renderer = me.grid.customRenderers[rec.get(me.grid.nameField)],
122229             result = val;
122230
122231         if (renderer) {
122232             return renderer.apply(me, arguments);
122233         }
122234         if (Ext.isDate(val)) {
122235             result = me.renderDate(val);
122236         } else if (Ext.isBoolean(val)) {
122237             result = me.renderBool(val);
122238         }
122239         return Ext.util.Format.htmlEncode(result);
122240     },
122241
122242     // private
122243     renderDate : Ext.util.Format.date,
122244
122245     // private
122246     renderBool : function(bVal) {
122247         return this[bVal ? 'trueText' : 'falseText'];
122248     },
122249
122250     // private
122251     // Renders custom property names instead of raw names if defined in the Grid
122252     getPropertyName : function(name) {
122253         var pn = this.grid.propertyNames;
122254         return pn && pn[name] ? pn[name] : name;
122255     }
122256 });
122257 /**
122258  * @class Ext.grid.property.Property
122259  * A specific {@link Ext.data.Model} type that represents a name/value pair and is made to work with the
122260  * {@link Ext.grid.property.Grid}.  Typically, Properties do not need to be created directly as they can be
122261  * created implicitly by simply using the appropriate data configs either via the {@link Ext.grid.property.Grid#source}
122262  * config property or by calling {@link Ext.grid.property.Grid#setSource}.  However, if the need arises, these records
122263  * can also be created explicitly as shown below.  Example usage:
122264  * <pre><code>
122265 var rec = new Ext.grid.property.Property({
122266     name: 'birthday',
122267     value: Ext.Date.parse('17/06/1962', 'd/m/Y')
122268 });
122269 // Add record to an already populated grid
122270 grid.store.addSorted(rec);
122271 </code></pre>
122272  * @constructor
122273  * @param {Object} config A data object in the format:<pre><code>
122274 {
122275     name: [name],
122276     value: [value]
122277 }</code></pre>
122278  * The specified value's type
122279  * will be read automatically by the grid to determine the type of editor to use when displaying it.
122280  */
122281 Ext.define('Ext.grid.property.Property', {
122282     extend: 'Ext.data.Model',
122283
122284     alternateClassName: 'Ext.PropGridProperty',
122285
122286     fields: [{
122287         name: 'name',
122288         type: 'string'
122289     }, {
122290         name: 'value'
122291     }],
122292     idProperty: 'name'
122293 });
122294 /**
122295  * @class Ext.grid.property.Store
122296  * @extends Ext.data.Store
122297  * A custom {@link Ext.data.Store} for the {@link Ext.grid.property.Grid}. This class handles the mapping
122298  * between the custom data source objects supported by the grid and the {@link Ext.grid.property.Property} format
122299  * used by the {@link Ext.data.Store} base class.
122300  */
122301 Ext.define('Ext.grid.property.Store', {
122302
122303     extend: 'Ext.data.Store',
122304
122305     alternateClassName: 'Ext.grid.PropertyStore',
122306
122307     uses: ['Ext.data.reader.Reader', 'Ext.data.proxy.Proxy', 'Ext.data.ResultSet', 'Ext.grid.property.Property'],
122308
122309     /**
122310      * Creates new property store.
122311      * @param {Ext.grid.Panel} grid The grid this store will be bound to
122312      * @param {Object} source The source data config object
122313      */
122314     constructor : function(grid, source){
122315         var me = this;
122316         
122317         me.grid = grid;
122318         me.source = source;
122319         me.callParent([{
122320             data: source,
122321             model: Ext.grid.property.Property,
122322             proxy: me.getProxy()
122323         }]);
122324     },
122325
122326     // Return a singleton, customized Proxy object which configures itself with a custom Reader
122327     getProxy: function() {
122328         if (!this.proxy) {
122329             Ext.grid.property.Store.prototype.proxy = Ext.create('Ext.data.proxy.Memory', {
122330                 model: Ext.grid.property.Property,
122331                 reader: this.getReader()
122332             });
122333         }
122334         return this.proxy;
122335     },
122336
122337     // Return a singleton, customized Reader object which reads Ext.grid.property.Property records from an object.
122338     getReader: function() {
122339         if (!this.reader) {
122340             Ext.grid.property.Store.prototype.reader = Ext.create('Ext.data.reader.Reader', {
122341                 model: Ext.grid.property.Property,
122342
122343                 buildExtractors: Ext.emptyFn,
122344
122345                 read: function(dataObject) {
122346                     return this.readRecords(dataObject);
122347                 },
122348
122349                 readRecords: function(dataObject) {
122350                     var val,
122351                         propName,
122352                         result = {
122353                             records: [],
122354                             success: true
122355                         };
122356
122357                     for (propName in dataObject) {
122358                         if (dataObject.hasOwnProperty(propName)) {
122359                             val = dataObject[propName];
122360                             if (this.isEditableValue(val)) {
122361                                 result.records.push(new Ext.grid.property.Property({
122362                                     name: propName,
122363                                     value: val
122364                                 }, propName));
122365                             }
122366                         }
122367                     }
122368                     result.total = result.count = result.records.length;
122369                     return Ext.create('Ext.data.ResultSet', result);
122370                 },
122371
122372                 // private
122373                 isEditableValue: function(val){
122374                     return Ext.isPrimitive(val) || Ext.isDate(val);
122375                 }
122376             });
122377         }
122378         return this.reader;
122379     },
122380
122381     // protected - should only be called by the grid.  Use grid.setSource instead.
122382     setSource : function(dataObject) {
122383         var me = this;
122384
122385         me.source = dataObject;
122386         me.suspendEvents();
122387         me.removeAll();
122388         me.proxy.data = dataObject;
122389         me.load();
122390         me.resumeEvents();
122391         me.fireEvent('datachanged', me);
122392     },
122393
122394     // private
122395     getProperty : function(row) {
122396        return Ext.isNumber(row) ? this.getAt(row) : this.getById(row);
122397     },
122398
122399     // private
122400     setValue : function(prop, value, create){
122401         var me = this,
122402             rec = me.getRec(prop);
122403             
122404         if (rec) {
122405             rec.set('value', value);
122406             me.source[prop] = value;
122407         } else if (create) {
122408             // only create if specified.
122409             me.source[prop] = value;
122410             rec = new Ext.grid.property.Property({name: prop, value: value}, prop);
122411             me.add(rec);
122412         }
122413     },
122414
122415     // private
122416     remove : function(prop) {
122417         var rec = this.getRec(prop);
122418         if (rec) {
122419             this.callParent([rec]);
122420             delete this.source[prop];
122421         }
122422     },
122423
122424     // private
122425     getRec : function(prop) {
122426         return this.getById(prop);
122427     },
122428
122429     // protected - should only be called by the grid.  Use grid.getSource instead.
122430     getSource : function() {
122431         return this.source;
122432     }
122433 });
122434 /**
122435  * Component layout for components which maintain an inner body element which must be resized to synchronize with the
122436  * Component size.
122437  * @class Ext.layout.component.Body
122438  * @extends Ext.layout.component.Component
122439  * @private
122440  */
122441
122442 Ext.define('Ext.layout.component.Body', {
122443
122444     /* Begin Definitions */
122445
122446     alias: ['layout.body'],
122447
122448     extend: 'Ext.layout.component.Component',
122449
122450     uses: ['Ext.layout.container.Container'],
122451
122452     /* End Definitions */
122453
122454     type: 'body',
122455     
122456     onLayout: function(width, height) {
122457         var me = this,
122458             owner = me.owner;
122459
122460         // Size the Component's encapsulating element according to the dimensions
122461         me.setTargetSize(width, height);
122462
122463         // Size the Component's body element according to the content box of the encapsulating element
122464         me.setBodySize.apply(me, arguments);
122465
122466         // We need to bind to the owner whenever we do not have a user set height or width.
122467         if (owner && owner.layout && owner.layout.isLayout) {
122468             if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
122469                 owner.layout.bindToOwnerCtComponent = true;
122470             }
122471             else {
122472                 owner.layout.bindToOwnerCtComponent = false;
122473             }
122474         }
122475         
122476         me.callParent(arguments);
122477     },
122478
122479     /**
122480      * @private
122481      * <p>Sizes the Component's body element to fit exactly within the content box of the Component's encapsulating element.<p>
122482      */
122483     setBodySize: function(width, height) {
122484         var me = this,
122485             owner = me.owner,
122486             frameSize = owner.frameSize,
122487             isNumber = Ext.isNumber;
122488
122489         if (isNumber(width)) {
122490             width -= owner.el.getFrameWidth('lr') - frameSize.left - frameSize.right;
122491         }
122492         if (isNumber(height)) {
122493             height -= owner.el.getFrameWidth('tb') - frameSize.top - frameSize.bottom;
122494         }
122495
122496         me.setElementSize(owner.body, width, height);
122497     }
122498 });
122499 /**
122500  * Component layout for Ext.form.FieldSet components
122501  * @class Ext.layout.component.FieldSet
122502  * @extends Ext.layout.component.Body
122503  * @private
122504  */
122505 Ext.define('Ext.layout.component.FieldSet', {
122506     extend: 'Ext.layout.component.Body',
122507     alias: ['layout.fieldset'],
122508
122509     type: 'fieldset',
122510
122511     doContainerLayout: function() {
122512         // Prevent layout/rendering of children if the fieldset is collapsed
122513         if (!this.owner.collapsed) {
122514             this.callParent();
122515         }
122516     }
122517 });
122518 /**
122519  * Component layout for tabs
122520  * @class Ext.layout.component.Tab
122521  * @extends Ext.layout.component.Button
122522  * @private
122523  */
122524 Ext.define('Ext.layout.component.Tab', {
122525
122526     alias: ['layout.tab'],
122527
122528     extend: 'Ext.layout.component.Button',
122529
122530     //type: 'button',
122531
122532     beforeLayout: function() {
122533         var me = this, dirty = me.lastClosable !== me.owner.closable;
122534
122535         if (dirty) {
122536             delete me.adjWidth;
122537         }
122538
122539         return this.callParent(arguments) || dirty;
122540     },
122541
122542     onLayout: function () {
122543         var me = this;
122544
122545         me.callParent(arguments);
122546
122547         me.lastClosable = me.owner.closable;
122548     }
122549 });
122550 /**
122551  * @private
122552  * @class Ext.layout.component.field.File
122553  * @extends Ext.layout.component.field.Field
122554  * Layout class for {@link Ext.form.field.File} fields. Adjusts the input field size to accommodate
122555  * the file picker trigger button.
122556  * @private
122557  */
122558
122559 Ext.define('Ext.layout.component.field.File', {
122560     alias: ['layout.filefield'],
122561     extend: 'Ext.layout.component.field.Field',
122562
122563     type: 'filefield',
122564
122565     sizeBodyContents: function(width, height) {
122566         var me = this,
122567             owner = me.owner;
122568
122569         if (!owner.buttonOnly) {
122570             // Decrease the field's width by the width of the button and the configured buttonMargin.
122571             // Both the text field and the button are floated left in CSS so they'll stack up side by side.
122572             me.setElementSize(owner.inputEl, Ext.isNumber(width) ? width - owner.button.getWidth() - owner.buttonMargin : width);
122573         }
122574     }
122575 });
122576 /**
122577  * @class Ext.layout.component.field.Slider
122578  * @extends Ext.layout.component.field.Field
122579  * @private
122580  */
122581
122582 Ext.define('Ext.layout.component.field.Slider', {
122583
122584     /* Begin Definitions */
122585
122586     alias: ['layout.sliderfield'],
122587
122588     extend: 'Ext.layout.component.field.Field',
122589
122590     /* End Definitions */
122591
122592     type: 'sliderfield',
122593
122594     sizeBodyContents: function(width, height) {
122595         var owner = this.owner,
122596             thumbs = owner.thumbs,
122597             length = thumbs.length,
122598             inputEl = owner.inputEl,
122599             innerEl = owner.innerEl,
122600             endEl = owner.endEl,
122601             i = 0;
122602
122603         /*
122604          * If we happen to be animating during a resize, the position of the thumb will likely be off
122605          * when the animation stops. As such, just stop any animations before syncing the thumbs.
122606          */
122607         for(; i < length; ++i) {
122608             thumbs[i].el.stopAnimation();
122609         }
122610         
122611         if (owner.vertical) {
122612             inputEl.setHeight(height);
122613             innerEl.setHeight(Ext.isNumber(height) ? height - inputEl.getPadding('t') - endEl.getPadding('b') : height);
122614         }
122615         else {
122616             inputEl.setWidth(width);
122617             innerEl.setWidth(Ext.isNumber(width) ? width - inputEl.getPadding('l') - endEl.getPadding('r') : width);
122618         }
122619         owner.syncThumbs();
122620     }
122621 });
122622
122623 /**
122624  * @class Ext.layout.container.Absolute
122625  * @extends Ext.layout.container.Anchor
122626  *
122627  * This is a layout that inherits the anchoring of {@link Ext.layout.container.Anchor} and adds the
122628  * ability for x/y positioning using the standard x and y component config options.
122629  *
122630  * This class is intended to be extended or created via the {@link Ext.container.Container#layout layout}
122631  * configuration property.  See {@link Ext.container.Container#layout} for additional details.
122632  *
122633  *     @example
122634  *     Ext.create('Ext.form.Panel', {
122635  *         title: 'Absolute Layout',
122636  *         width: 300,
122637  *         height: 275,
122638  *         layout:'absolute',
122639  *         layoutConfig: {
122640  *             // layout-specific configs go here
122641  *             //itemCls: 'x-abs-layout-item',
122642  *         },
122643  *         url:'save-form.php',
122644  *         defaultType: 'textfield',
122645  *         items: [{
122646  *             x: 10,
122647  *             y: 10,
122648  *             xtype:'label',
122649  *             text: 'Send To:'
122650  *         },{
122651  *             x: 80,
122652  *             y: 10,
122653  *             name: 'to',
122654  *             anchor:'90%'  // anchor width by percentage
122655  *         },{
122656  *             x: 10,
122657  *             y: 40,
122658  *             xtype:'label',
122659  *             text: 'Subject:'
122660  *         },{
122661  *             x: 80,
122662  *             y: 40,
122663  *             name: 'subject',
122664  *             anchor: '90%'  // anchor width by percentage
122665  *         },{
122666  *             x:0,
122667  *             y: 80,
122668  *             xtype: 'textareafield',
122669  *             name: 'msg',
122670  *             anchor: '100% 100%'  // anchor width and height
122671  *         }],
122672  *         renderTo: Ext.getBody()
122673  *     });
122674  */
122675 Ext.define('Ext.layout.container.Absolute', {
122676
122677     /* Begin Definitions */
122678
122679     alias: 'layout.absolute',
122680     extend: 'Ext.layout.container.Anchor',
122681     alternateClassName: 'Ext.layout.AbsoluteLayout',
122682
122683     /* End Definitions */
122684
122685     itemCls: Ext.baseCSSPrefix + 'abs-layout-item',
122686
122687     type: 'absolute',
122688
122689     onLayout: function() {
122690         var me = this,
122691             target = me.getTarget(),
122692             targetIsBody = target.dom === document.body;
122693
122694         // Do not set position: relative; when the absolute layout target is the body
122695         if (!targetIsBody) {
122696             target.position();
122697         }
122698         me.paddingLeft = target.getPadding('l');
122699         me.paddingTop = target.getPadding('t');
122700         me.callParent(arguments);
122701     },
122702
122703     // private
122704     adjustWidthAnchor: function(value, comp) {
122705         //return value ? value - comp.getPosition(true)[0] + this.paddingLeft: value;
122706         return value ? value - comp.getPosition(true)[0] : value;
122707     },
122708
122709     // private
122710     adjustHeightAnchor: function(value, comp) {
122711         //return value ? value - comp.getPosition(true)[1] + this.paddingTop: value;
122712         return value ? value - comp.getPosition(true)[1] : value;
122713     }
122714 });
122715 /**
122716  * @class Ext.layout.container.Accordion
122717  * @extends Ext.layout.container.VBox
122718  *
122719  * This is a layout that manages multiple Panels in an expandable accordion style such that only
122720  * **one Panel can be expanded at any given time**. Each Panel has built-in support for expanding and collapsing.
122721  *
122722  * Note: Only Ext Panels and all subclasses of Ext.panel.Panel may be used in an accordion layout Container.
122723  *
122724  *     @example
122725  *     Ext.create('Ext.panel.Panel', {
122726  *         title: 'Accordion Layout',
122727  *         width: 300,
122728  *         height: 300,
122729  *         layout:'accordion',
122730  *         defaults: {
122731  *             // applied to each contained panel
122732  *             bodyStyle: 'padding:15px'
122733  *         },
122734  *         layoutConfig: {
122735  *             // layout-specific configs go here
122736  *             titleCollapse: false,
122737  *             animate: true,
122738  *             activeOnTop: true
122739  *         },
122740  *         items: [{
122741  *             title: 'Panel 1',
122742  *             html: 'Panel content!'
122743  *         },{
122744  *             title: 'Panel 2',
122745  *             html: 'Panel content!'
122746  *         },{
122747  *             title: 'Panel 3',
122748  *             html: 'Panel content!'
122749  *         }],
122750  *         renderTo: Ext.getBody()
122751  *     });
122752  */
122753 Ext.define('Ext.layout.container.Accordion', {
122754     extend: 'Ext.layout.container.VBox',
122755     alias: ['layout.accordion'],
122756     alternateClassName: 'Ext.layout.AccordionLayout',
122757
122758     itemCls: Ext.baseCSSPrefix + 'box-item ' + Ext.baseCSSPrefix + 'accordion-item',
122759
122760     align: 'stretch',
122761
122762     /**
122763      * @cfg {Boolean} fill
122764      * True to adjust the active item's height to fill the available space in the container, false to use the
122765      * item's current height, or auto height if not explicitly set.
122766      */
122767     fill : true,
122768
122769     /**
122770      * @cfg {Boolean} autoWidth
122771      * Child Panels have their width actively managed to fit within the accordion's width.
122772      * @deprecated This config is ignored in ExtJS 4
122773      */
122774     autoWidth : true,
122775
122776     /**
122777      * @cfg {Boolean} titleCollapse
122778      * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow
122779      * expand/collapse only when the toggle tool button is clicked.  When set to false,
122780      * {@link #hideCollapseTool} should be false also.
122781      */
122782     titleCollapse : true,
122783
122784     /**
122785      * @cfg {Boolean} hideCollapseTool
122786      * True to hide the contained Panels' collapse/expand toggle buttons, false to display them.
122787      * When set to true, {@link #titleCollapse} is automatically set to <code>true</code>.
122788      */
122789     hideCollapseTool : false,
122790
122791     /**
122792      * @cfg {Boolean} collapseFirst
122793      * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools
122794      * in the contained Panels' title bars, false to render it last.
122795      */
122796     collapseFirst : false,
122797
122798     /**
122799      * @cfg {Boolean} animate
122800      * True to slide the contained panels open and closed during expand/collapse using animation, false to open and
122801      * close directly with no animation. Note: The layout performs animated collapsing
122802      * and expanding, <i>not</i> the child Panels.
122803      */
122804     animate : true,
122805     /**
122806      * @cfg {Boolean} activeOnTop
122807      * Only valid when {@link #multi} is `false` and {@link #animate} is `false`.
122808      *
122809      * True to swap the position of each panel as it is expanded so that it becomes the first item in the container,
122810      * false to keep the panels in the rendered order.
122811      */
122812     activeOnTop : false,
122813     /**
122814      * @cfg {Boolean} multi
122815      * Set to <code>true</code> to enable multiple accordion items to be open at once.
122816      */
122817     multi: false,
122818
122819     constructor: function() {
122820         var me = this;
122821
122822         me.callParent(arguments);
122823
122824         // animate flag must be false during initial render phase so we don't get animations.
122825         me.initialAnimate = me.animate;
122826         me.animate = false;
122827
122828         // Child Panels are not absolutely positioned if we are not filling, so use a different itemCls.
122829         if (me.fill === false) {
122830             me.itemCls = Ext.baseCSSPrefix + 'accordion-item';
122831         }
122832     },
122833
122834     // Cannot lay out a fitting accordion before we have been allocated a height.
122835     // So during render phase, layout will not be performed.
122836     beforeLayout: function() {
122837         var me = this;
122838
122839         me.callParent(arguments);
122840         if (me.fill) {
122841             if (!(me.owner.el.dom.style.height || me.getLayoutTargetSize().height)) {
122842                 return false;
122843             }
122844         } else {
122845             me.owner.componentLayout.monitorChildren = false;
122846             me.autoSize = true;
122847             me.owner.setAutoScroll(true);
122848         }
122849     },
122850
122851     renderItems : function(items, target) {
122852         var me = this,
122853             ln = items.length,
122854             i = 0,
122855             comp,
122856             targetSize = me.getLayoutTargetSize(),
122857             renderedPanels = [];
122858
122859         for (; i < ln; i++) {
122860             comp = items[i];
122861             if (!comp.rendered) {
122862                 renderedPanels.push(comp);
122863
122864                 // Set up initial properties for Panels in an accordion.
122865                 if (me.collapseFirst) {
122866                     comp.collapseFirst = me.collapseFirst;
122867                 }
122868                 if (me.hideCollapseTool) {
122869                     comp.hideCollapseTool = me.hideCollapseTool;
122870                     comp.titleCollapse = true;
122871                 }
122872                 else if (me.titleCollapse) {
122873                     comp.titleCollapse = me.titleCollapse;
122874                 }
122875
122876                 delete comp.hideHeader;
122877                 comp.collapsible = true;
122878                 comp.title = comp.title || '&#160;';
122879
122880                 // Set initial sizes
122881                 comp.width = targetSize.width;
122882                 if (me.fill) {
122883                     delete comp.height;
122884                     delete comp.flex;
122885
122886                     // If there is an expanded item, all others must be rendered collapsed.
122887                     if (me.expandedItem !== undefined) {
122888                         comp.collapsed = true;
122889                     }
122890                     // Otherwise expand the first item with collapsed explicitly configured as false
122891                     else if (comp.hasOwnProperty('collapsed') && comp.collapsed === false) {
122892                         comp.flex = 1;
122893                         me.expandedItem = i;
122894                     } else {
122895                         comp.collapsed = true;
122896                     }
122897                     // If we are fitting, then intercept expand/collapse requests.
122898                     me.owner.mon(comp, {
122899                         show: me.onComponentShow,
122900                         beforeexpand: me.onComponentExpand,
122901                         beforecollapse: me.onComponentCollapse,
122902                         scope: me
122903                     });
122904                 } else {
122905                     delete comp.flex;
122906                     comp.animCollapse = me.initialAnimate;
122907                     comp.autoHeight = true;
122908                     comp.autoScroll = false;
122909                 }
122910                 comp.border = comp.collapsed;
122911             }
122912         }
122913
122914         // If no collapsed:false Panels found, make the first one expanded.
122915         if (ln && me.expandedItem === undefined) {
122916             me.expandedItem = 0;
122917             comp = items[0];
122918             comp.collapsed = comp.border = false;
122919             if (me.fill) {
122920                 comp.flex = 1;
122921             }
122922         }
122923
122924         // Render all Panels.
122925         me.callParent(arguments);
122926
122927         // Postprocess rendered Panels.
122928         ln = renderedPanels.length;
122929         for (i = 0; i < ln; i++) {
122930             comp = renderedPanels[i];
122931
122932             // Delete the dimension property so that our align: 'stretch' processing manages the width from here
122933             delete comp.width;
122934
122935             comp.header.addCls(Ext.baseCSSPrefix + 'accordion-hd');
122936             comp.body.addCls(Ext.baseCSSPrefix + 'accordion-body');
122937         }
122938     },
122939
122940     onLayout: function() {
122941         var me = this;
122942
122943
122944         if (me.fill) {
122945             me.callParent(arguments);
122946         } else {
122947             var targetSize = me.getLayoutTargetSize(),
122948                 items = me.getVisibleItems(),
122949                 len = items.length,
122950                 i = 0, comp;
122951
122952             for (; i < len; i++) {
122953                 comp = items[i];
122954                 if (comp.collapsed) {
122955                     items[i].setWidth(targetSize.width);
122956                 } else {
122957                     items[i].setSize(null, null);
122958                 }
122959             }
122960         }
122961         me.updatePanelClasses();
122962
122963         return me;
122964     },
122965
122966     updatePanelClasses: function() {
122967         var children = this.getLayoutItems(),
122968             ln = children.length,
122969             siblingCollapsed = true,
122970             i, child;
122971
122972         for (i = 0; i < ln; i++) {
122973             child = children[i];
122974
122975             // Fix for EXTJSIV-3724. Windows only.
122976             // Collapsing the Psnel's el to a size which only allows a single hesder to be visible, scrolls the header out of view.
122977             if (Ext.isWindows) {
122978                 child.el.dom.scrollTop = 0;
122979             }
122980
122981             if (siblingCollapsed) {
122982                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
122983             }
122984             else {
122985                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-sibling-expanded');
122986             }
122987
122988             if (i + 1 == ln && child.collapsed) {
122989                 child.header.addCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
122990             }
122991             else {
122992                 child.header.removeCls(Ext.baseCSSPrefix + 'accordion-hd-last-collapsed');
122993             }
122994             siblingCollapsed = child.collapsed;
122995         }
122996     },
122997     
122998     animCallback: function(){
122999         Ext.Array.forEach(this.toCollapse, function(comp){
123000             comp.fireEvent('collapse', comp);
123001         });
123002         
123003         Ext.Array.forEach(this.toExpand, function(comp){
123004             comp.fireEvent('expand', comp);
123005         });    
123006     },
123007     
123008     setupEvents: function(){
123009         this.toCollapse = [];
123010         this.toExpand = [];    
123011     },
123012
123013     // When a Component expands, adjust the heights of the other Components to be just enough to accommodate
123014     // their headers.
123015     // The expanded Component receives the only flex value, and so gets all remaining space.
123016     onComponentExpand: function(toExpand) {
123017         var me = this,
123018             it = me.owner.items.items,
123019             len = it.length,
123020             i = 0,
123021             comp;
123022
123023         me.setupEvents();
123024         for (; i < len; i++) {
123025             comp = it[i];
123026             if (comp === toExpand && comp.collapsed) {
123027                 me.setExpanded(comp);
123028             } else if (!me.multi && (comp.rendered && comp.header.rendered && comp !== toExpand && !comp.collapsed)) {
123029                 me.setCollapsed(comp);
123030             }
123031         }
123032
123033         me.animate = me.initialAnimate;
123034         if (me.activeOnTop) {
123035             // insert will trigger a layout
123036             me.owner.insert(0, toExpand); 
123037         } else {
123038             me.layout();
123039         }
123040         me.animate = false;
123041         return false;
123042     },
123043
123044     onComponentCollapse: function(comp) {
123045         var me = this,
123046             toExpand = comp.next() || comp.prev(),
123047             expanded = me.multi ? me.owner.query('>panel:not([collapsed])') : [];
123048
123049         me.setupEvents();
123050         // If we are allowing multi, and the "toCollapse" component is NOT the only expanded Component,
123051         // then ask the box layout to collapse it to its header.
123052         if (me.multi) {
123053             me.setCollapsed(comp);
123054
123055             // If the collapsing Panel is the only expanded one, expand the following Component.
123056             // All this is handling fill: true, so there must be at least one expanded,
123057             if (expanded.length === 1 && expanded[0] === comp) {
123058                 me.setExpanded(toExpand);
123059             }
123060
123061             me.animate = me.initialAnimate;
123062             me.layout();
123063             me.animate = false;
123064         }
123065         // Not allowing multi: expand the next sibling if possible, prev sibling if we collapsed the last
123066         else if (toExpand) {
123067             me.onComponentExpand(toExpand);
123068         }
123069         return false;
123070     },
123071
123072     onComponentShow: function(comp) {
123073         // Showing a Component means that you want to see it, so expand it.
123074         this.onComponentExpand(comp);
123075     },
123076
123077     setCollapsed: function(comp) {
123078         var otherDocks = comp.getDockedItems(),
123079             dockItem,
123080             len = otherDocks.length,
123081             i = 0;
123082
123083         // Hide all docked items except the header
123084         comp.hiddenDocked = [];
123085         for (; i < len; i++) {
123086             dockItem = otherDocks[i];
123087             if ((dockItem !== comp.header) && !dockItem.hidden) {
123088                 dockItem.hidden = true;
123089                 comp.hiddenDocked.push(dockItem);
123090             }
123091         }
123092         comp.addCls(comp.collapsedCls);
123093         comp.header.addCls(comp.collapsedHeaderCls);
123094         comp.height = comp.header.getHeight();
123095         comp.el.setHeight(comp.height);
123096         comp.collapsed = true;
123097         delete comp.flex;
123098         if (this.initialAnimate) {
123099             this.toCollapse.push(comp);
123100         } else {
123101             comp.fireEvent('collapse', comp);
123102         }
123103         if (comp.collapseTool) {
123104             comp.collapseTool.setType('expand-' + comp.getOppositeDirection(comp.collapseDirection));
123105         }
123106     },
123107
123108     setExpanded: function(comp) {
123109         var otherDocks = comp.hiddenDocked,
123110             len = otherDocks ? otherDocks.length : 0,
123111             i = 0;
123112
123113         // Show temporarily hidden docked items
123114         for (; i < len; i++) {
123115             otherDocks[i].show();
123116         }
123117
123118         // If it was an initial native collapse which hides the body
123119         if (!comp.body.isVisible()) {
123120             comp.body.show();
123121         }
123122         delete comp.collapsed;
123123         delete comp.height;
123124         delete comp.componentLayout.lastComponentSize;
123125         comp.suspendLayout = false;
123126         comp.flex = 1;
123127         comp.removeCls(comp.collapsedCls);
123128         comp.header.removeCls(comp.collapsedHeaderCls);
123129          if (this.initialAnimate) {
123130             this.toExpand.push(comp);
123131         } else {
123132             comp.fireEvent('expand', comp);
123133         }
123134         if (comp.collapseTool) {
123135             comp.collapseTool.setType('collapse-' + comp.collapseDirection);
123136         }
123137         comp.setAutoScroll(comp.initialConfig.autoScroll);
123138     }
123139 });
123140 /**
123141  * This class functions between siblings of a {@link Ext.layout.container.VBox VBox} or {@link Ext.layout.container.HBox HBox}
123142  * layout to resize both immediate siblings.
123143  *
123144  * By default it will set the size of both siblings. <b>One</b> of the siblings may be configured with
123145  * `{@link Ext.Component#maintainFlex maintainFlex}: true` which will cause it not to receive a new size explicitly, but to be resized
123146  * by the layout.
123147  *
123148  * A Splitter may be configured to show a centered mini-collapse tool orientated to collapse the {@link #collapseTarget}.
123149  * The Splitter will then call that sibling Panel's {@link Ext.panel.Panel#collapse collapse} or {@link Ext.panel.Panel#expand expand} method
123150  * to perform the appropriate operation (depending on the sibling collapse state). To create the mini-collapse tool but take care
123151  * of collapsing yourself, configure the splitter with <code>{@link #performCollapse} false</code>.
123152  */
123153 Ext.define('Ext.resizer.Splitter', {
123154     extend: 'Ext.Component',
123155     requires: ['Ext.XTemplate'],
123156     uses: ['Ext.resizer.SplitterTracker'],
123157     alias: 'widget.splitter',
123158
123159     renderTpl: [
123160         '<tpl if="collapsible===true">',
123161             '<div id="{id}-collapseEl" class="', Ext.baseCSSPrefix, 'collapse-el ',
123162                     Ext.baseCSSPrefix, 'layout-split-{collapseDir}">&nbsp;</div>',
123163         '</tpl>'
123164     ],
123165
123166     baseCls: Ext.baseCSSPrefix + 'splitter',
123167     collapsedClsInternal: Ext.baseCSSPrefix + 'splitter-collapsed',
123168
123169     /**
123170      * @cfg {Boolean} collapsible
123171      * <code>true</code> to show a mini-collapse tool in the Splitter to toggle expand and collapse on the {@link #collapseTarget} Panel.
123172      * Defaults to the {@link Ext.panel.Panel#collapsible collapsible} setting of the Panel.
123173      */
123174     collapsible: false,
123175
123176     /**
123177      * @cfg {Boolean} performCollapse
123178      * <p>Set to <code>false</code> to prevent this Splitter's mini-collapse tool from managing the collapse
123179      * state of the {@link #collapseTarget}.</p>
123180      */
123181
123182     /**
123183      * @cfg {Boolean} collapseOnDblClick
123184      * <code>true</code> to enable dblclick to toggle expand and collapse on the {@link #collapseTarget} Panel.
123185      */
123186     collapseOnDblClick: true,
123187
123188     /**
123189      * @cfg {Number} defaultSplitMin
123190      * Provides a default minimum width or height for the two components
123191      * that the splitter is between.
123192      */
123193     defaultSplitMin: 40,
123194
123195     /**
123196      * @cfg {Number} defaultSplitMax
123197      * Provides a default maximum width or height for the two components
123198      * that the splitter is between.
123199      */
123200     defaultSplitMax: 1000,
123201
123202     /**
123203      * @cfg {String} collapsedCls
123204      * A class to add to the splitter when it is collapsed. See {@link #collapsible}.
123205      */
123206
123207     width: 5,
123208     height: 5,
123209
123210     /**
123211      * @cfg {String/Ext.panel.Panel} collapseTarget
123212      * <p>A string describing the relative position of the immediate sibling Panel to collapse. May be 'prev' or 'next' (Defaults to 'next')</p>
123213      * <p>Or the immediate sibling Panel to collapse.</p>
123214      * <p>The orientation of the mini-collapse tool will be inferred from this setting.</p>
123215      * <p><b>Note that only Panels may be collapsed.</b></p>
123216      */
123217     collapseTarget: 'next',
123218
123219     /**
123220      * @property orientation
123221      * @type String
123222      * Orientation of this Splitter. <code>'vertical'</code> when used in an hbox layout, <code>'horizontal'</code>
123223      * when used in a vbox layout.
123224      */
123225
123226     onRender: function() {
123227         var me = this,
123228             target = me.getCollapseTarget(),
123229             collapseDir = me.getCollapseDirection();
123230
123231         Ext.applyIf(me.renderData, {
123232             collapseDir: collapseDir,
123233             collapsible: me.collapsible || target.collapsible
123234         });
123235
123236         me.addChildEls('collapseEl');
123237
123238         this.callParent(arguments);
123239
123240         // Add listeners on the mini-collapse tool unless performCollapse is set to false
123241         if (me.performCollapse !== false) {
123242             if (me.renderData.collapsible) {
123243                 me.mon(me.collapseEl, 'click', me.toggleTargetCmp, me);
123244             }
123245             if (me.collapseOnDblClick) {
123246                 me.mon(me.el, 'dblclick', me.toggleTargetCmp, me);
123247             }
123248         }
123249
123250         // Ensure the mini collapse icon is set to the correct direction when the target is collapsed/expanded by any means
123251         me.mon(target, 'collapse', me.onTargetCollapse, me);
123252         me.mon(target, 'expand', me.onTargetExpand, me);
123253
123254         me.el.addCls(me.baseCls + '-' + me.orientation);
123255         me.el.unselectable();
123256
123257         me.tracker = Ext.create('Ext.resizer.SplitterTracker', {
123258             el: me.el
123259         });
123260
123261         // Relay the most important events to our owner (could open wider later):
123262         me.relayEvents(me.tracker, [ 'beforedragstart', 'dragstart', 'dragend' ]);
123263     },
123264
123265     getCollapseDirection: function() {
123266         var me = this,
123267             idx,
123268             type = me.ownerCt.layout.type;
123269
123270         // Avoid duplication of string tests.
123271         // Create a two bit truth table of the configuration of the Splitter:
123272         // Collapse Target | orientation
123273         //        0              0             = next, horizontal
123274         //        0              1             = next, vertical
123275         //        1              0             = prev, horizontal
123276         //        1              1             = prev, vertical
123277         if (me.collapseTarget.isComponent) {
123278             idx = Number(me.ownerCt.items.indexOf(me.collapseTarget) == me.ownerCt.items.indexOf(me) - 1) << 1 | Number(type == 'hbox');
123279         } else {
123280             idx = Number(me.collapseTarget == 'prev') << 1 | Number(type == 'hbox');
123281         }
123282
123283         // Read the data out the truth table
123284         me.orientation = ['horizontal', 'vertical'][idx & 1];
123285         return ['bottom', 'right', 'top', 'left'][idx];
123286     },
123287
123288     getCollapseTarget: function() {
123289         var me = this;
123290
123291         return me.collapseTarget.isComponent ? me.collapseTarget : me.collapseTarget == 'prev' ? me.previousSibling() : me.nextSibling();
123292     },
123293
123294     onTargetCollapse: function(target) {
123295         this.el.addCls([this.collapsedClsInternal, this.collapsedCls]);
123296     },
123297
123298     onTargetExpand: function(target) {
123299         this.el.removeCls([this.collapsedClsInternal, this.collapsedCls]);
123300     },
123301
123302     toggleTargetCmp: function(e, t) {
123303         var cmp = this.getCollapseTarget();
123304
123305         if (cmp.isVisible()) {
123306             // restore
123307             if (cmp.collapsed) {
123308                 cmp.expand(cmp.animCollapse);
123309             // collapse
123310             } else {
123311                 cmp.collapse(this.renderData.collapseDir, cmp.animCollapse);
123312             }
123313         }
123314     },
123315
123316     /*
123317      * Work around IE bug. %age margins do not get recalculated on element resize unless repaint called.
123318      */
123319     setSize: function() {
123320         var me = this;
123321         me.callParent(arguments);
123322         if (Ext.isIE) {
123323             me.el.repaint();
123324         }
123325     }
123326 });
123327
123328 /**
123329  * This is a multi-pane, application-oriented UI layout style that supports multiple nested panels, automatic bars
123330  * between regions and built-in {@link Ext.panel.Panel#collapsible expanding and collapsing} of regions.
123331  *
123332  * This class is intended to be extended or created via the `layout:'border'` {@link Ext.container.Container#layout}
123333  * config, and should generally not need to be created directly via the new keyword.
123334  *
123335  *     @example
123336  *     Ext.create('Ext.panel.Panel', {
123337  *         width: 500,
123338  *         height: 400,
123339  *         title: 'Border Layout',
123340  *         layout: 'border',
123341  *         items: [{
123342  *             title: 'South Region is resizable',
123343  *             region: 'south',     // position for region
123344  *             xtype: 'panel',
123345  *             height: 100,
123346  *             split: true,         // enable resizing
123347  *             margins: '0 5 5 5'
123348  *         },{
123349  *             // xtype: 'panel' implied by default
123350  *             title: 'West Region is collapsible',
123351  *             region:'west',
123352  *             xtype: 'panel',
123353  *             margins: '5 0 0 5',
123354  *             width: 200,
123355  *             collapsible: true,   // make collapsible
123356  *             id: 'west-region-container',
123357  *             layout: 'fit'
123358  *         },{
123359  *             title: 'Center Region',
123360  *             region: 'center',     // center region is required, no width/height specified
123361  *             xtype: 'panel',
123362  *             layout: 'fit',
123363  *             margins: '5 5 0 0'
123364  *         }],
123365  *         renderTo: Ext.getBody()
123366  *     });
123367  *
123368  * # Notes
123369  *
123370  * - Any Container using the Border layout **must** have a child item with `region:'center'`.
123371  *   The child item in the center region will always be resized to fill the remaining space
123372  *   not used by the other regions in the layout.
123373  *
123374  * - Any child items with a region of `west` or `east` may be configured with either an initial
123375  *   `width`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage width
123376  *   **string** (Which is simply divided by 100 and used as a flex value).
123377  *   The 'center' region has a flex value of `1`.
123378  *
123379  * - Any child items with a region of `north` or `south` may be configured with either an initial
123380  *   `height`, or a {@link Ext.layout.container.Box#flex} value, or an initial percentage height
123381  *   **string** (Which is simply divided by 100 and used as a flex value).
123382  *   The 'center' region has a flex value of `1`.
123383  *
123384  * - The regions of a BorderLayout are **fixed at render time** and thereafter, its child
123385  *   Components may not be removed or added**. To add/remove Components within a BorderLayout,
123386  *   have them wrapped by an additional Container which is directly managed by the BorderLayout.
123387  *   If the region is to be collapsible, the Container used directly by the BorderLayout manager
123388  *   should be a Panel. In the following example a Container (an Ext.panel.Panel) is added to
123389  *   the west region:
123390  *
123391  *       wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
123392  *       wrc.{@link Ext.container.Container#removeAll removeAll}();
123393  *       wrc.{@link Ext.container.Container#add add}({
123394  *           title: 'Added Panel',
123395  *           html: 'Some content'
123396  *       });
123397  *
123398  * - **There is no BorderLayout.Region class in ExtJS 4.0+**
123399  */
123400 Ext.define('Ext.layout.container.Border', {
123401
123402     alias: ['layout.border'],
123403     extend: 'Ext.layout.container.Container',
123404     requires: ['Ext.resizer.Splitter', 'Ext.container.Container', 'Ext.fx.Anim'],
123405     alternateClassName: 'Ext.layout.BorderLayout',
123406
123407     targetCls: Ext.baseCSSPrefix + 'border-layout-ct',
123408
123409     itemCls: Ext.baseCSSPrefix + 'border-item',
123410
123411     bindToOwnerCtContainer: true,
123412
123413     percentageRe: /(\d+)%/,
123414
123415     slideDirection: {
123416         north: 't',
123417         south: 'b',
123418         west: 'l',
123419         east: 'r'
123420     },
123421
123422     constructor: function(config) {
123423         this.initialConfig = config;
123424         this.callParent(arguments);
123425     },
123426
123427     onLayout: function() {
123428         var me = this;
123429         if (!me.borderLayoutInitialized) {
123430             me.initializeBorderLayout();
123431         }
123432
123433         // Delegate this operation to the shadow "V" or "H" box layout, and then down to any embedded layout.
123434         me.fixHeightConstraints();
123435         me.shadowLayout.onLayout();
123436         if (me.embeddedContainer) {
123437             me.embeddedContainer.layout.onLayout();
123438         }
123439
123440         // If the panel was originally configured with collapsed: true, it will have
123441         // been initialized with a "borderCollapse" flag: Collapse it now before the first layout.
123442         if (!me.initialCollapsedComplete) {
123443             Ext.iterate(me.regions, function(name, region){
123444                 if (region.borderCollapse) {
123445                     me.onBeforeRegionCollapse(region, region.collapseDirection, false, 0);
123446                 }
123447             });
123448             me.initialCollapsedComplete = true;
123449         }
123450     },
123451
123452     isValidParent : function(item, target, position) {
123453         if (!this.borderLayoutInitialized) {
123454             this.initializeBorderLayout();
123455         }
123456
123457         // Delegate this operation to the shadow "V" or "H" box layout.
123458         return this.shadowLayout.isValidParent(item, target, position);
123459     },
123460
123461     beforeLayout: function() {
123462         if (!this.borderLayoutInitialized) {
123463             this.initializeBorderLayout();
123464         }
123465
123466         // Delegate this operation to the shadow "V" or "H" box layout.
123467         this.shadowLayout.beforeLayout();
123468
123469         // note: don't call base because that does a renderItems again
123470     },
123471
123472     renderItems: function(items, target) {
123473     },
123474
123475     renderItem: function(item) {
123476     },
123477
123478     renderChildren: function() {
123479         if (!this.borderLayoutInitialized) {
123480             this.initializeBorderLayout();
123481         }
123482
123483         this.shadowLayout.renderChildren();
123484     },
123485
123486     /*
123487      * Gathers items for a layout operation. Injected into child Box layouts through configuration.
123488      * We must not include child items which are floated over the layout (are primed with a slide out animation)
123489      */
123490     getVisibleItems: function() {
123491         return Ext.ComponentQuery.query(':not([slideOutAnim])', this.callParent(arguments));
123492     },
123493
123494     initializeBorderLayout: function() {
123495         var me = this,
123496             i = 0,
123497             items = me.getLayoutItems(),
123498             ln = items.length,
123499             regions = (me.regions = {}),
123500             vBoxItems = [],
123501             hBoxItems = [],
123502             horizontalFlex = 0,
123503             verticalFlex = 0,
123504             comp, percentage;
123505
123506         // Map of Splitters for each region
123507         me.splitters = {};
123508
123509         // Map of regions
123510         for (; i < ln; i++) {
123511             comp = items[i];
123512             regions[comp.region] = comp;
123513
123514             // Intercept collapsing to implement showing an alternate Component as a collapsed placeholder
123515             if (comp.region != 'center' && comp.collapsible && comp.collapseMode != 'header') {
123516
123517                 // This layout intercepts any initial collapsed state. Panel must not do this itself.
123518                 comp.borderCollapse = comp.collapsed;
123519                 comp.collapsed = false;
123520
123521                 comp.on({
123522                     beforecollapse: me.onBeforeRegionCollapse,
123523                     beforeexpand: me.onBeforeRegionExpand,
123524                     destroy: me.onRegionDestroy,
123525                     scope: me
123526                 });
123527                 me.setupState(comp);
123528             }
123529         }
123530         comp = regions.center;
123531         if (!comp.flex) {
123532             comp.flex = 1;
123533         }
123534         delete comp.width;
123535         comp.maintainFlex = true;
123536
123537         // Begin the VBox and HBox item list.
123538         comp = regions.west;
123539         if (comp) {
123540             comp.collapseDirection = Ext.Component.DIRECTION_LEFT;
123541             hBoxItems.push(comp);
123542             if (comp.split) {
123543                 hBoxItems.push(me.splitters.west = me.createSplitter(comp));
123544             }
123545             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
123546             if (percentage) {
123547                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
123548                 delete comp.width;
123549             }
123550         }
123551         comp = regions.north;
123552         if (comp) {
123553             comp.collapseDirection = Ext.Component.DIRECTION_TOP;
123554             vBoxItems.push(comp);
123555             if (comp.split) {
123556                 vBoxItems.push(me.splitters.north = me.createSplitter(comp));
123557             }
123558             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
123559             if (percentage) {
123560                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
123561                 delete comp.height;
123562             }
123563         }
123564
123565         // Decide into which Collection the center region goes.
123566         if (regions.north || regions.south) {
123567             if (regions.east || regions.west) {
123568
123569                 // Create the embedded center. Mark it with the region: 'center' property so that it can be identified as the center.
123570                 vBoxItems.push(me.embeddedContainer = Ext.create('Ext.container.Container', {
123571                     xtype: 'container',
123572                     region: 'center',
123573                     id: me.owner.id + '-embedded-center',
123574                     cls: Ext.baseCSSPrefix + 'border-item',
123575                     flex: regions.center.flex,
123576                     maintainFlex: true,
123577                     layout: {
123578                         type: 'hbox',
123579                         align: 'stretch',
123580                         getVisibleItems: me.getVisibleItems
123581                     }
123582                 }));
123583                 hBoxItems.push(regions.center);
123584             }
123585             // No east or west: the original center goes straight into the vbox
123586             else {
123587                 vBoxItems.push(regions.center);
123588             }
123589         }
123590         // If we have no north or south, then the center is part of the HBox items
123591         else {
123592             hBoxItems.push(regions.center);
123593         }
123594
123595         // Finish off the VBox and HBox item list.
123596         comp = regions.south;
123597         if (comp) {
123598             comp.collapseDirection = Ext.Component.DIRECTION_BOTTOM;
123599             if (comp.split) {
123600                 vBoxItems.push(me.splitters.south = me.createSplitter(comp));
123601             }
123602             percentage = Ext.isString(comp.height) && comp.height.match(me.percentageRe);
123603             if (percentage) {
123604                 verticalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
123605                 delete comp.height;
123606             }
123607             vBoxItems.push(comp);
123608         }
123609         comp = regions.east;
123610         if (comp) {
123611             comp.collapseDirection = Ext.Component.DIRECTION_RIGHT;
123612             if (comp.split) {
123613                 hBoxItems.push(me.splitters.east = me.createSplitter(comp));
123614             }
123615             percentage = Ext.isString(comp.width) && comp.width.match(me.percentageRe);
123616             if (percentage) {
123617                 horizontalFlex += (comp.flex = parseInt(percentage[1], 10) / 100);
123618                 delete comp.width;
123619             }
123620             hBoxItems.push(comp);
123621         }
123622
123623         // Create the injected "items" collections for the Containers.
123624         // If we have north or south, then the shadow Container will be a VBox.
123625         // If there are also east or west regions, its center will be a shadow HBox.
123626         // If there are *only* east or west regions, then the shadow layout will be an HBox (or Fit).
123627         if (regions.north || regions.south) {
123628
123629             me.shadowContainer = Ext.create('Ext.container.Container', {
123630                 ownerCt: me.owner,
123631                 el: me.getTarget(),
123632                 layout: Ext.applyIf({
123633                     type: 'vbox',
123634                     align: 'stretch',
123635                     getVisibleItems: me.getVisibleItems
123636                 }, me.initialConfig)
123637             });
123638             me.createItems(me.shadowContainer, vBoxItems);
123639
123640             // Allow the Splitters to orientate themselves
123641             if (me.splitters.north) {
123642                 me.splitters.north.ownerCt = me.shadowContainer;
123643             }
123644             if (me.splitters.south) {
123645                 me.splitters.south.ownerCt = me.shadowContainer;
123646             }
123647
123648             // Inject items into the HBox Container if there is one - if there was an east or west.
123649             if (me.embeddedContainer) {
123650                 me.embeddedContainer.ownerCt = me.shadowContainer;
123651                 me.createItems(me.embeddedContainer, hBoxItems);
123652
123653                 // Allow the Splitters to orientate themselves
123654                 if (me.splitters.east) {
123655                     me.splitters.east.ownerCt = me.embeddedContainer;
123656                 }
123657                 if (me.splitters.west) {
123658                     me.splitters.west.ownerCt = me.embeddedContainer;
123659                 }
123660
123661                 // These spliiters need to be constrained by components one-level below
123662                 // the component in their vobx. We update the min/maxHeight on the helper
123663                 // (embeddedContainer) prior to starting the split/drag. This has to be
123664                 // done on-the-fly to allow min/maxHeight of the E/C/W regions to be set
123665                 // dynamically.
123666                 Ext.each([me.splitters.north, me.splitters.south], function (splitter) {
123667                     if (splitter) {
123668                         splitter.on('beforedragstart', me.fixHeightConstraints, me);
123669                     }
123670                 });
123671
123672                 // The east or west region wanted a percentage
123673                 if (horizontalFlex) {
123674                     regions.center.flex -= horizontalFlex;
123675                 }
123676                 // The north or south region wanted a percentage
123677                 if (verticalFlex) {
123678                     me.embeddedContainer.flex -= verticalFlex;
123679                 }
123680             } else {
123681                 // The north or south region wanted a percentage
123682                 if (verticalFlex) {
123683                     regions.center.flex -= verticalFlex;
123684                 }
123685             }
123686         }
123687         // If we have no north or south, then there's only one Container, and it's
123688         // an HBox, or, if only a center region was specified, a Fit.
123689         else {
123690             me.shadowContainer = Ext.create('Ext.container.Container', {
123691                 ownerCt: me.owner,
123692                 el: me.getTarget(),
123693                 layout: Ext.applyIf({
123694                     type: (hBoxItems.length == 1) ? 'fit' : 'hbox',
123695                     align: 'stretch'
123696                 }, me.initialConfig)
123697             });
123698             me.createItems(me.shadowContainer, hBoxItems);
123699
123700             // Allow the Splitters to orientate themselves
123701             if (me.splitters.east) {
123702                 me.splitters.east.ownerCt = me.shadowContainer;
123703             }
123704             if (me.splitters.west) {
123705                 me.splitters.west.ownerCt = me.shadowContainer;
123706             }
123707
123708             // The east or west region wanted a percentage
123709             if (horizontalFlex) {
123710                 regions.center.flex -= verticalFlex;
123711             }
123712         }
123713
123714         // Create upward links from the region Components to their shadow ownerCts
123715         for (i = 0, items = me.shadowContainer.items.items, ln = items.length; i < ln; i++) {
123716             items[i].shadowOwnerCt = me.shadowContainer;
123717         }
123718         if (me.embeddedContainer) {
123719             for (i = 0, items = me.embeddedContainer.items.items, ln = items.length; i < ln; i++) {
123720                 items[i].shadowOwnerCt = me.embeddedContainer;
123721             }
123722         }
123723
123724         // This is the layout that we delegate all operations to
123725         me.shadowLayout = me.shadowContainer.getLayout();
123726
123727         me.borderLayoutInitialized = true;
123728     },
123729
123730     setupState: function(comp){
123731         var getState = comp.getState;
123732         comp.getState = function(){
123733             // call the original getState
123734             var state = getState.call(comp) || {},
123735                 region = comp.region;
123736
123737             state.collapsed = !!comp.collapsed;
123738             if (region == 'west' || region == 'east') {
123739                 state.width = comp.getWidth();
123740             } else {
123741                 state.height = comp.getHeight();
123742             }
123743             return state;
123744         };
123745         comp.addStateEvents(['collapse', 'expand', 'resize']);
123746     },
123747
123748     /**
123749      * Create the items collection for our shadow/embedded containers
123750      * @private
123751      */
123752     createItems: function(container, items){
123753         // Have to inject an items Collection *after* construction.
123754         // The child items of the shadow layout must retain their original, user-defined ownerCt
123755         delete container.items;
123756         container.initItems();
123757         container.items.addAll(items);
123758     },
123759
123760     // Private
123761     // Create a splitter for a child of the layout.
123762     createSplitter: function(comp) {
123763         var me = this,
123764             interceptCollapse = (comp.collapseMode != 'header'),
123765             resizer;
123766
123767         resizer = Ext.create('Ext.resizer.Splitter', {
123768             hidden: !!comp.hidden,
123769             collapseTarget: comp,
123770             performCollapse: !interceptCollapse,
123771             listeners: interceptCollapse ? {
123772                 click: {
123773                     fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
123774                     element: 'collapseEl'
123775                 }
123776             } : null
123777         });
123778
123779         // Mini collapse means that the splitter is the placeholder Component
123780         if (comp.collapseMode == 'mini') {
123781             comp.placeholder = resizer;
123782             resizer.collapsedCls = comp.collapsedCls;
123783         }
123784
123785         // Arrange to hide/show a region's associated splitter when the region is hidden/shown
123786         comp.on({
123787             hide: me.onRegionVisibilityChange,
123788             show: me.onRegionVisibilityChange,
123789             scope: me
123790         });
123791         return resizer;
123792     },
123793
123794     // Private
123795     // Propagates the min/maxHeight values from the inner hbox items to its container.
123796     fixHeightConstraints: function () {
123797         var me = this,
123798             ct = me.embeddedContainer,
123799             maxHeight = 1e99, minHeight = -1;
123800
123801         if (!ct) {
123802             return;
123803         }
123804
123805         ct.items.each(function (item) {
123806             if (Ext.isNumber(item.maxHeight)) {
123807                 maxHeight = Math.max(maxHeight, item.maxHeight);
123808             }
123809             if (Ext.isNumber(item.minHeight)) {
123810                 minHeight = Math.max(minHeight, item.minHeight);
123811             }
123812         });
123813
123814         ct.maxHeight = maxHeight;
123815         ct.minHeight = minHeight;
123816     },
123817
123818     // Hide/show a region's associated splitter when the region is hidden/shown
123819     onRegionVisibilityChange: function(comp){
123820         this.splitters[comp.region][comp.hidden ? 'hide' : 'show']();
123821         this.layout();
123822     },
123823
123824     // Called when a splitter mini-collapse tool is clicked on.
123825     // The listener is only added if this layout is controlling collapsing,
123826     // not if the component's collapseMode is 'mini' or 'header'.
123827     onSplitterCollapseClick: function(comp) {
123828         if (comp.collapsed) {
123829             this.onPlaceHolderToolClick(null, null, null, {client: comp});
123830         } else {
123831             comp.collapse();
123832         }
123833     },
123834
123835     /**
123836      * Return the {@link Ext.panel.Panel#placeholder placeholder} Component to which the passed child Panel of the
123837      * layout will collapse. By default, this will be a {@link Ext.panel.Header Header} component (Docked to the
123838      * appropriate border). See {@link Ext.panel.Panel#placeholder placeholder}. config to customize this.
123839      *
123840      * **Note that this will be a fully instantiated Component, but will only be _rendered_ when the Panel is first
123841      * collapsed.**
123842      * @param {Ext.panel.Panel} panel The child Panel of the layout for which to return the {@link
123843      * Ext.panel.Panel#placeholder placeholder}.
123844      * @return {Ext.Component} The Panel's {@link Ext.panel.Panel#placeholder placeholder} unless the {@link
123845      * Ext.panel.Panel#collapseMode collapseMode} is `'header'`, in which case _undefined_ is returned.
123846      */
123847     getPlaceholder: function(comp) {
123848         var me = this,
123849             placeholder = comp.placeholder,
123850             shadowContainer = comp.shadowOwnerCt,
123851             shadowLayout = shadowContainer.layout,
123852             oppositeDirection = Ext.panel.Panel.prototype.getOppositeDirection(comp.collapseDirection),
123853             horiz = (comp.region == 'north' || comp.region == 'south');
123854
123855         // No placeholder if the collapse mode is not the Border layout default
123856         if (comp.collapseMode == 'header') {
123857             return;
123858         }
123859
123860         // Provide a replacement Container with an expand tool
123861         if (!placeholder) {
123862             if (comp.collapseMode == 'mini') {
123863                 placeholder = Ext.create('Ext.resizer.Splitter', {
123864                     id: 'collapse-placeholder-' + comp.id,
123865                     collapseTarget: comp,
123866                     performCollapse: false,
123867                     listeners: {
123868                         click: {
123869                             fn: Ext.Function.bind(me.onSplitterCollapseClick, me, [comp]),
123870                             element: 'collapseEl'
123871                         }
123872                     }
123873                 });
123874                 placeholder.addCls(placeholder.collapsedCls);
123875             } else {
123876                 placeholder = {
123877                     id: 'collapse-placeholder-' + comp.id,
123878                     margins: comp.initialConfig.margins || Ext.getClass(comp).prototype.margins,
123879                     xtype: 'header',
123880                     orientation: horiz ? 'horizontal' : 'vertical',
123881                     title: comp.title,
123882                     textCls: comp.headerTextCls,
123883                     iconCls: comp.iconCls,
123884                     baseCls: comp.baseCls + '-header',
123885                     ui: comp.ui,
123886                     indicateDrag: comp.draggable,
123887                     cls: Ext.baseCSSPrefix + 'region-collapsed-placeholder ' + Ext.baseCSSPrefix + 'region-collapsed-' + comp.collapseDirection + '-placeholder ' + comp.collapsedCls,
123888                     listeners: comp.floatable ? {
123889                         click: {
123890                             fn: function(e) {
123891                                 me.floatCollapsedPanel(e, comp);
123892                             },
123893                             element: 'el'
123894                         }
123895                     } : null
123896                 };
123897                 // Hack for IE6/7/IEQuirks's inability to display an inline-block
123898                 if ((Ext.isIE6 || Ext.isIE7 || (Ext.isIEQuirks)) && !horiz) {
123899                     placeholder.width = 25;
123900                 }
123901                 if (!comp.hideCollapseTool) {
123902                     placeholder[horiz ? 'tools' : 'items'] = [{
123903                         xtype: 'tool',
123904                         client: comp,
123905                         type: 'expand-' + oppositeDirection,
123906                         handler: me.onPlaceHolderToolClick,
123907                         scope: me
123908                     }];
123909                 }
123910             }
123911             placeholder = me.owner.createComponent(placeholder);
123912             if (comp.isXType('panel')) {
123913                 comp.on({
123914                     titlechange: me.onRegionTitleChange,
123915                     iconchange: me.onRegionIconChange,
123916                     scope: me
123917                 });
123918             }
123919         }
123920
123921         // The collapsed Component holds a reference to its placeholder and vice versa
123922         comp.placeholder = placeholder;
123923         placeholder.comp = comp;
123924
123925         return placeholder;
123926     },
123927
123928     /**
123929      * @private
123930      * Update the placeholder title when panel title has been set or changed.
123931      */
123932     onRegionTitleChange: function(comp, newTitle) {
123933         comp.placeholder.setTitle(newTitle);
123934     },
123935
123936     /**
123937      * @private
123938      * Update the placeholder iconCls when panel iconCls has been set or changed.
123939      */
123940     onRegionIconChange: function(comp, newIconCls) {
123941         comp.placeholder.setIconCls(newIconCls);
123942     },
123943
123944     /**
123945      * @private
123946      * Calculates the size and positioning of the passed child item. Must be present because Panel's expand,
123947      * when configured with a flex, calls this method on its ownerCt's layout.
123948      * @param {Ext.Component} child The child Component to calculate the box for
123949      * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
123950      */
123951     calculateChildBox: function(comp) {
123952         var me = this;
123953         if (me.shadowContainer.items.contains(comp)) {
123954             return me.shadowContainer.layout.calculateChildBox(comp);
123955         }
123956         else if (me.embeddedContainer && me.embeddedContainer.items.contains(comp)) {
123957             return me.embeddedContainer.layout.calculateChildBox(comp);
123958         }
123959     },
123960
123961     /**
123962      * @private
123963      * Intercepts the Panel's own collapse event and perform's substitution of the Panel
123964      * with a placeholder Header orientated in the appropriate dimension.
123965      * @param comp The Panel being collapsed.
123966      * @param direction
123967      * @param animate
123968      * @returns {Boolean} false to inhibit the Panel from performing its own collapse.
123969      */
123970     onBeforeRegionCollapse: function(comp, direction, animate) {
123971         if (comp.collapsedChangingLayout) {
123972             return false;
123973         }
123974         comp.collapsedChangingLayout = true;
123975         var me = this,
123976             compEl = comp.el,
123977             width,
123978             miniCollapse = comp.collapseMode == 'mini',
123979             shadowContainer = comp.shadowOwnerCt,
123980             shadowLayout = shadowContainer.layout,
123981             placeholder = comp.placeholder,
123982             sl = me.owner.suspendLayout,
123983             scsl = shadowContainer.suspendLayout,
123984             isNorthOrWest = (comp.region == 'north' || comp.region == 'west'); // Flag to keep the placeholder non-adjacent to any Splitter
123985
123986         // Do not trigger a layout during transition to collapsed Component
123987         me.owner.suspendLayout = true;
123988         shadowContainer.suspendLayout = true;
123989
123990         // Prevent upward notifications from downstream layouts
123991         shadowLayout.layoutBusy = true;
123992         if (shadowContainer.componentLayout) {
123993             shadowContainer.componentLayout.layoutBusy = true;
123994         }
123995         me.shadowContainer.layout.layoutBusy = true;
123996         me.layoutBusy = true;
123997         me.owner.componentLayout.layoutBusy = true;
123998
123999         // Provide a replacement Container with an expand tool
124000         if (!placeholder) {
124001             placeholder = me.getPlaceholder(comp);
124002         }
124003
124004         // placeholder already in place; show it.
124005         if (placeholder.shadowOwnerCt === shadowContainer) {
124006             placeholder.show();
124007         }
124008         // Insert the collapsed placeholder Component into the appropriate Box layout shadow Container
124009         // It must go next to its client Component, but non-adjacent to the splitter so splitter can find its collapse client.
124010         // Inject an ownerCt value pointing to the owner, border layout Container as the user will expect.
124011         else {
124012             shadowContainer.insert(shadowContainer.items.indexOf(comp) + (isNorthOrWest ? 0 : 1), placeholder);
124013             placeholder.shadowOwnerCt = shadowContainer;
124014             placeholder.ownerCt = me.owner;
124015         }
124016
124017         // Flag the collapsing Component as hidden and show the placeholder.
124018         // This causes the shadow Box layout's calculateChildBoxes to calculate the correct new arrangement.
124019         // We hide or slideOut the Component's element
124020         comp.hidden = true;
124021
124022         if (!placeholder.rendered) {
124023             shadowLayout.renderItem(placeholder, shadowLayout.innerCt);
124024
124025             // The inserted placeholder does not have the proper size, so copy the width
124026             // for N/S or the height for E/W from the component. This fixes EXTJSIV-1562
124027             // without recursive layouts. This is only an issue initially. After this time,
124028             // placeholder will have the correct width/height set by the layout (which has
124029             // already happened when we get here initially).
124030             if (comp.region == 'north' || comp.region == 'south') {
124031                 placeholder.setCalculatedSize(comp.getWidth());
124032             } else {
124033                 placeholder.setCalculatedSize(undefined, comp.getHeight());
124034             }
124035         }
124036
124037         // Jobs to be done after the collapse has been done
124038         function afterCollapse() {
124039             // Reinstate automatic laying out.
124040             me.owner.suspendLayout = sl;
124041             shadowContainer.suspendLayout = scsl;
124042             delete shadowLayout.layoutBusy;
124043             if (shadowContainer.componentLayout) {
124044                 delete shadowContainer.componentLayout.layoutBusy;
124045             }
124046             delete me.shadowContainer.layout.layoutBusy;
124047             delete me.layoutBusy;
124048             delete me.owner.componentLayout.layoutBusy;
124049             delete comp.collapsedChangingLayout;
124050
124051             // Fire the collapse event: The Panel has in fact been collapsed, but by substitution of an alternative Component
124052             comp.collapsed = true;
124053             comp.fireEvent('collapse', comp);
124054         }
124055
124056         /*
124057          * Set everything to the new positions. Note that we
124058          * only want to animate the collapse if it wasn't configured
124059          * initially with collapsed: true
124060          */
124061         if (comp.animCollapse && me.initialCollapsedComplete) {
124062             shadowLayout.layout();
124063             compEl.dom.style.zIndex = 100;
124064
124065             // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
124066             if (!miniCollapse) {
124067                 placeholder.el.hide();
124068             }
124069             compEl.slideOut(me.slideDirection[comp.region], {
124070                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
124071                 listeners: {
124072                     afteranimate: function() {
124073                         compEl.show().setLeftTop(-10000, -10000);
124074                         compEl.dom.style.zIndex = '';
124075
124076                         // If we're mini-collapsing, the placholder is a Splitter. We don't want it to "bounce in"
124077                        if (!miniCollapse) {
124078                             placeholder.el.slideIn(me.slideDirection[comp.region], {
124079                                 easing: 'linear',
124080                                 duration: 100
124081                             });
124082                         }
124083                         afterCollapse();
124084                     }
124085                 }
124086             });
124087         } else {
124088             compEl.setLeftTop(-10000, -10000);
124089             shadowLayout.layout();
124090             afterCollapse();
124091         }
124092
124093         return false;
124094     },
124095
124096     // Hijack the expand operation to remove the placeholder and slide the region back in.
124097     onBeforeRegionExpand: function(comp, animate) {
124098         // We don't check for comp.collapsedChangingLayout here because onPlaceHolderToolClick does it
124099         this.onPlaceHolderToolClick(null, null, null, {client: comp, shouldFireBeforeexpand: false});
124100         return false;
124101     },
124102
124103     // Called when the collapsed placeholder is clicked to reinstate a "collapsed" (in reality hidden) Panel.
124104     onPlaceHolderToolClick: function(e, target, owner, tool) {
124105         var me = this,
124106             comp = tool.client,
124107
124108             // Hide the placeholder unless it was the Component's preexisting splitter
124109             hidePlaceholder = (comp.collapseMode != 'mini') || !comp.split,
124110             compEl = comp.el,
124111             toCompBox,
124112             placeholder = comp.placeholder,
124113             placeholderEl = placeholder.el,
124114             shadowContainer = comp.shadowOwnerCt,
124115             shadowLayout = shadowContainer.layout,
124116             curSize,
124117             sl = me.owner.suspendLayout,
124118             scsl = shadowContainer.suspendLayout,
124119             isFloating;
124120
124121         if (comp.collapsedChangingLayout) {
124122             return false;
124123         }
124124         if (tool.shouldFireBeforeexpand !== false && comp.fireEvent('beforeexpand', comp, true) === false) {
124125             return false;
124126         }
124127         comp.collapsedChangingLayout = true;
124128         // If the slide in is still going, stop it.
124129         // This will either leave the Component in its fully floated state (which is processed below)
124130         // or in its collapsed state. Either way, we expand it..
124131         if (comp.getActiveAnimation()) {
124132             comp.stopAnimation();
124133         }
124134
124135         // If the Component is fully floated when they click the placeholder Tool,
124136         // it will be primed with a slide out animation object... so delete that
124137         // and remove the mouseout listeners
124138         if (comp.slideOutAnim) {
124139             // Remove mouse leave monitors
124140             compEl.un(comp.panelMouseMon);
124141             placeholderEl.un(comp.placeholderMouseMon);
124142
124143             delete comp.slideOutAnim;
124144             delete comp.panelMouseMon;
124145             delete comp.placeholderMouseMon;
124146
124147             // If the Panel was floated and primed with a slideOut animation, we don't want to animate its layout operation.
124148             isFloating = true;
124149         }
124150
124151         // Do not trigger a layout during transition to expanded Component
124152         me.owner.suspendLayout = true;
124153         shadowContainer.suspendLayout = true;
124154
124155         // Prevent upward notifications from downstream layouts
124156         shadowLayout.layoutBusy = true;
124157         if (shadowContainer.componentLayout) {
124158             shadowContainer.componentLayout.layoutBusy = true;
124159         }
124160         me.shadowContainer.layout.layoutBusy = true;
124161         me.layoutBusy = true;
124162         me.owner.componentLayout.layoutBusy = true;
124163
124164         // Unset the hidden and collapsed flags set in onBeforeRegionCollapse. The shadowLayout will now take it into account
124165         // Find where the shadow Box layout plans to put the expanding Component.
124166         comp.hidden = false;
124167         comp.collapsed = false;
124168         if (hidePlaceholder) {
124169             placeholder.hidden = true;
124170         }
124171         toCompBox = shadowLayout.calculateChildBox(comp);
124172
124173         // Show the collapse tool in case it was hidden by the slide-in
124174         if (comp.collapseTool) {
124175             comp.collapseTool.show();
124176         }
124177
124178         // If we're going to animate, we need to hide the component before moving it back into position
124179         if (comp.animCollapse && !isFloating) {
124180             compEl.setStyle('visibility', 'hidden');
124181         }
124182         compEl.setLeftTop(toCompBox.left, toCompBox.top);
124183
124184         // Equalize the size of the expanding Component prior to animation
124185         // in case the layout area has changed size during the time it was collapsed.
124186         curSize = comp.getSize();
124187         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
124188             me.setItemSize(comp, toCompBox.width, toCompBox.height);
124189         }
124190
124191         // Jobs to be done after the expand has been done
124192         function afterExpand() {
124193             // Reinstate automatic laying out.
124194             me.owner.suspendLayout = sl;
124195             shadowContainer.suspendLayout = scsl;
124196             delete shadowLayout.layoutBusy;
124197             if (shadowContainer.componentLayout) {
124198                 delete shadowContainer.componentLayout.layoutBusy;
124199             }
124200             delete me.shadowContainer.layout.layoutBusy;
124201             delete me.layoutBusy;
124202             delete me.owner.componentLayout.layoutBusy;
124203             delete comp.collapsedChangingLayout;
124204
124205             // In case it was floated out and they clicked the re-expand tool
124206             comp.removeCls(Ext.baseCSSPrefix + 'border-region-slide-in');
124207
124208             // Fire the expand event: The Panel has in fact been expanded, but by removal of an alternative Component
124209             comp.fireEvent('expand', comp);
124210         }
124211
124212         // Hide the placeholder
124213         if (hidePlaceholder) {
124214             placeholder.el.hide();
124215         }
124216
124217         // Slide the expanding Component to its new position.
124218         // When that is done, layout the layout.
124219         if (comp.animCollapse && !isFloating) {
124220             compEl.dom.style.zIndex = 100;
124221             compEl.slideIn(me.slideDirection[comp.region], {
124222                 duration: Ext.Number.from(comp.animCollapse, Ext.fx.Anim.prototype.duration),
124223                 listeners: {
124224                     afteranimate: function() {
124225                         compEl.dom.style.zIndex = '';
124226                         comp.hidden = false;
124227                         shadowLayout.onLayout();
124228                         afterExpand();
124229                     }
124230                 }
124231             });
124232         } else {
124233             shadowLayout.onLayout();
124234             afterExpand();
124235         }
124236     },
124237
124238     floatCollapsedPanel: function(e, comp) {
124239
124240         if (comp.floatable === false) {
124241             return;
124242         }
124243
124244         var me = this,
124245             compEl = comp.el,
124246             placeholder = comp.placeholder,
124247             placeholderEl = placeholder.el,
124248             shadowContainer = comp.shadowOwnerCt,
124249             shadowLayout = shadowContainer.layout,
124250             placeholderBox = shadowLayout.getChildBox(placeholder),
124251             scsl = shadowContainer.suspendLayout,
124252             curSize, toCompBox, compAnim;
124253
124254         // Ignore clicks on tools.
124255         if (e.getTarget('.' + Ext.baseCSSPrefix + 'tool')) {
124256             return;
124257         }
124258
124259         // It's *being* animated, ignore the click.
124260         // Possible future enhancement: Stop and *reverse* the current active Fx.
124261         if (compEl.getActiveAnimation()) {
124262             return;
124263         }
124264
124265         // If the Component is already fully floated when they click the placeholder,
124266         // it will be primed with a slide out animation object... so slide it out.
124267         if (comp.slideOutAnim) {
124268             me.slideOutFloatedComponent(comp);
124269             return;
124270         }
124271
124272         // Function to be called when the mouse leaves the floated Panel
124273         // Slide out when the mouse leaves the region bounded by the slid Component and its placeholder.
124274         function onMouseLeaveFloated(e) {
124275             var slideRegion = compEl.getRegion().union(placeholderEl.getRegion()).adjust(1, -1, -1, 1);
124276
124277             // If mouse is not within slide Region, slide it out
124278             if (!slideRegion.contains(e.getPoint())) {
124279                 me.slideOutFloatedComponent(comp);
124280             }
124281         }
124282
124283         // Monitor for mouseouting of the placeholder. Hide it if they exit for half a second or more
124284         comp.placeholderMouseMon = placeholderEl.monitorMouseLeave(500, onMouseLeaveFloated);
124285
124286         // Do not trigger a layout during slide out of the Component
124287         shadowContainer.suspendLayout = true;
124288
124289         // Prevent upward notifications from downstream layouts
124290         me.layoutBusy = true;
124291         me.owner.componentLayout.layoutBusy = true;
124292
124293         // The collapse tool is hidden while slid.
124294         // It is re-shown on expand.
124295         if (comp.collapseTool) {
124296             comp.collapseTool.hide();
124297         }
124298
124299         // Set flags so that the layout will calculate the boxes for what we want
124300         comp.hidden = false;
124301         comp.collapsed = false;
124302         placeholder.hidden = true;
124303
124304         // Recalculate new arrangement of the Component being floated.
124305         toCompBox = shadowLayout.calculateChildBox(comp);
124306         placeholder.hidden = false;
124307
124308         // Component to appear just after the placeholder, whatever "after" means in the context of the shadow Box layout.
124309         if (comp.region == 'north' || comp.region == 'west') {
124310             toCompBox[shadowLayout.parallelBefore] += placeholderBox[shadowLayout.parallelPrefix] - 1;
124311         } else {
124312             toCompBox[shadowLayout.parallelBefore] -= (placeholderBox[shadowLayout.parallelPrefix] - 1);
124313         }
124314         compEl.setStyle('visibility', 'hidden');
124315         compEl.setLeftTop(toCompBox.left, toCompBox.top);
124316
124317         // Equalize the size of the expanding Component prior to animation
124318         // in case the layout area has changed size during the time it was collapsed.
124319         curSize = comp.getSize();
124320         if (curSize.height != toCompBox.height || curSize.width != toCompBox.width) {
124321             me.setItemSize(comp, toCompBox.width, toCompBox.height);
124322         }
124323
124324         // This animation slides the collapsed Component's el out to just beyond its placeholder
124325         compAnim = {
124326             listeners: {
124327                 afteranimate: function() {
124328                     shadowContainer.suspendLayout = scsl;
124329                     delete me.layoutBusy;
124330                     delete me.owner.componentLayout.layoutBusy;
124331
124332                     // Prime the Component with an Anim config object to slide it back out
124333                     compAnim.listeners = {
124334                         afterAnimate: function() {
124335                             compEl.show().removeCls(Ext.baseCSSPrefix + 'border-region-slide-in').setLeftTop(-10000, -10000);
124336
124337                             // Reinstate the correct, current state after slide out animation finishes
124338                             comp.hidden = true;
124339                             comp.collapsed = true;
124340                             delete comp.slideOutAnim;
124341                             delete comp.panelMouseMon;
124342                             delete comp.placeholderMouseMon;
124343                         }
124344                     };
124345                     comp.slideOutAnim = compAnim;
124346                 }
124347             },
124348             duration: 500
124349         };
124350
124351         // Give the element the correct class which places it at a high z-index
124352         compEl.addCls(Ext.baseCSSPrefix + 'border-region-slide-in');
124353
124354         // Begin the slide in
124355         compEl.slideIn(me.slideDirection[comp.region], compAnim);
124356
124357         // Monitor for mouseouting of the slid area. Hide it if they exit for half a second or more
124358         comp.panelMouseMon = compEl.monitorMouseLeave(500, onMouseLeaveFloated);
124359
124360     },
124361
124362     slideOutFloatedComponent: function(comp) {
124363         var compEl = comp.el,
124364             slideOutAnim;
124365
124366         // Remove mouse leave monitors
124367         compEl.un(comp.panelMouseMon);
124368         comp.placeholder.el.un(comp.placeholderMouseMon);
124369
124370         // Slide the Component out
124371         compEl.slideOut(this.slideDirection[comp.region], comp.slideOutAnim);
124372
124373         delete comp.slideOutAnim;
124374         delete comp.panelMouseMon;
124375         delete comp.placeholderMouseMon;
124376     },
124377
124378     /*
124379      * @private
124380      * Ensure any collapsed placeholder Component is destroyed along with its region.
124381      * Can't do this in onDestroy because they may remove a Component and use it elsewhere.
124382      */
124383     onRegionDestroy: function(comp) {
124384         var placeholder = comp.placeholder;
124385         if (placeholder) {
124386             delete placeholder.ownerCt;
124387             placeholder.destroy();
124388         }
124389     },
124390
124391     /*
124392      * @private
124393      * Ensure any shadow Containers are destroyed.
124394      * Ensure we don't keep references to Components.
124395      */
124396     onDestroy: function() {
124397         var me = this,
124398             shadowContainer = me.shadowContainer,
124399             embeddedContainer = me.embeddedContainer;
124400
124401         if (shadowContainer) {
124402             delete shadowContainer.ownerCt;
124403             Ext.destroy(shadowContainer);
124404         }
124405
124406         if (embeddedContainer) {
124407             delete embeddedContainer.ownerCt;
124408             Ext.destroy(embeddedContainer);
124409         }
124410         delete me.regions;
124411         delete me.splitters;
124412         delete me.shadowContainer;
124413         delete me.embeddedContainer;
124414         me.callParent(arguments);
124415     }
124416 });
124417
124418 /**
124419  * This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be
124420  * visible at any given time.  This layout style is most commonly used for wizards, tab implementations, etc.
124421  * This class is intended to be extended or created via the layout:'card' {@link Ext.container.Container#layout} config,
124422  * and should generally not need to be created directly via the new keyword.
124423  *
124424  * The CardLayout's focal method is {@link #setActiveItem}.  Since only one panel is displayed at a time,
124425  * the only way to move from one Component to the next is by calling setActiveItem, passing the next panel to display
124426  * (or its id or index).  The layout itself does not provide a user interface for handling this navigation,
124427  * so that functionality must be provided by the developer.
124428  *
124429  * To change the active card of a container, call the setActiveItem method of its layout:
124430  *
124431  *     Ext.create('Ext.panel.Panel', {
124432  *         layout: 'card',
124433  *         items: [
124434  *             { html: 'Card 1' },
124435  *             { html: 'Card 2' }
124436  *         ],
124437  *         renderTo: Ext.getBody()
124438  *     });
124439  *
124440  *     p.getLayout().setActiveItem(1);
124441  *
124442  * In the following example, a simplistic wizard setup is demonstrated.  A button bar is added
124443  * to the footer of the containing panel to provide navigation buttons.  The buttons will be handled by a
124444  * common navigation routine.  Note that other uses of a CardLayout (like a tab control) would require a
124445  * completely different implementation.  For serious implementations, a better approach would be to extend
124446  * CardLayout to provide the custom functionality needed.
124447  *
124448  *     @example
124449  *     var navigate = function(panel, direction){
124450  *         // This routine could contain business logic required to manage the navigation steps.
124451  *         // It would call setActiveItem as needed, manage navigation button state, handle any
124452  *         // branching logic that might be required, handle alternate actions like cancellation
124453  *         // or finalization, etc.  A complete wizard implementation could get pretty
124454  *         // sophisticated depending on the complexity required, and should probably be
124455  *         // done as a subclass of CardLayout in a real-world implementation.
124456  *         var layout = panel.getLayout();
124457  *         layout[direction]();
124458  *         Ext.getCmp('move-prev').setDisabled(!layout.getPrev());
124459  *         Ext.getCmp('move-next').setDisabled(!layout.getNext());
124460  *     };
124461  *
124462  *     Ext.create('Ext.panel.Panel', {
124463  *         title: 'Example Wizard',
124464  *         width: 300,
124465  *         height: 200,
124466  *         layout: 'card',
124467  *         bodyStyle: 'padding:15px',
124468  *         defaults: {
124469  *             // applied to each contained panel
124470  *             border: false
124471  *         },
124472  *         // just an example of one possible navigation scheme, using buttons
124473  *         bbar: [
124474  *             {
124475  *                 id: 'move-prev',
124476  *                 text: 'Back',
124477  *                 handler: function(btn) {
124478  *                     navigate(btn.up("panel"), "prev");
124479  *                 },
124480  *                 disabled: true
124481  *             },
124482  *             '->', // greedy spacer so that the buttons are aligned to each side
124483  *             {
124484  *                 id: 'move-next',
124485  *                 text: 'Next',
124486  *                 handler: function(btn) {
124487  *                     navigate(btn.up("panel"), "next");
124488  *                 }
124489  *             }
124490  *         ],
124491  *         // the panels (or "cards") within the layout
124492  *         items: [{
124493  *             id: 'card-0',
124494  *             html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
124495  *         },{
124496  *             id: 'card-1',
124497  *             html: '<p>Step 2 of 3</p>'
124498  *         },{
124499  *             id: 'card-2',
124500  *             html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
124501  *         }],
124502  *         renderTo: Ext.getBody()
124503  *     });
124504  */
124505 Ext.define('Ext.layout.container.Card', {
124506
124507     /* Begin Definitions */
124508
124509     alias: ['layout.card'],
124510     alternateClassName: 'Ext.layout.CardLayout',
124511
124512     extend: 'Ext.layout.container.AbstractCard',
124513
124514     /* End Definitions */
124515
124516     /**
124517      * Makes the given card active.
124518      *
124519      *     var card1 = Ext.create('Ext.panel.Panel', {itemId: 'card-1'});
124520      *     var card2 = Ext.create('Ext.panel.Panel', {itemId: 'card-2'});
124521      *     var panel = Ext.create('Ext.panel.Panel', {
124522      *         layout: 'card',
124523      *         activeItem: 0,
124524      *         items: [card1, card2]
124525      *     });
124526      *     // These are all equivalent
124527      *     panel.getLayout().setActiveItem(card2);
124528      *     panel.getLayout().setActiveItem('card-2');
124529      *     panel.getLayout().setActiveItem(1);
124530      *
124531      * @param {Ext.Component/Number/String} newCard  The component, component {@link Ext.Component#id id},
124532      * {@link Ext.Component#itemId itemId}, or index of component.
124533      * @return {Ext.Component} the activated component or false when nothing activated.
124534      * False is returned also when trying to activate an already active card.
124535      */
124536     setActiveItem: function(newCard) {
124537         var me = this,
124538             owner = me.owner,
124539             oldCard = me.activeItem,
124540             newIndex;
124541
124542         newCard = me.parseActiveItem(newCard);
124543         newIndex = owner.items.indexOf(newCard);
124544
124545         // If the card is not a child of the owner, then add it
124546         if (newIndex == -1) {
124547             newIndex = owner.items.items.length;
124548             owner.add(newCard);
124549         }
124550
124551         // Is this a valid, different card?
124552         if (newCard && oldCard != newCard) {
124553             // If the card has not been rendered yet, now is the time to do so.
124554             if (!newCard.rendered) {
124555                 me.renderItem(newCard, me.getRenderTarget(), owner.items.length);
124556                 me.configureItem(newCard, 0);
124557             }
124558
124559             me.activeItem = newCard;
124560
124561             // Fire the beforeactivate and beforedeactivate events on the cards
124562             if (newCard.fireEvent('beforeactivate', newCard, oldCard) === false) {
124563                 return false;
124564             }
124565             if (oldCard && oldCard.fireEvent('beforedeactivate', oldCard, newCard) === false) {
124566                 return false;
124567             }
124568
124569             // If the card hasnt been sized yet, do it now
124570             if (me.sizeAllCards) {
124571                 // onLayout calls setItemBox
124572                 me.onLayout();
124573             }
124574             else {
124575                 me.setItemBox(newCard, me.getTargetBox());
124576             }
124577
124578             me.owner.suspendLayout = true;
124579
124580             if (oldCard) {
124581                 if (me.hideInactive) {
124582                     oldCard.hide();
124583                 }
124584                 oldCard.fireEvent('deactivate', oldCard, newCard);
124585             }
124586
124587             // Make sure the new card is shown
124588             me.owner.suspendLayout = false;
124589             if (newCard.hidden) {
124590                 newCard.show();
124591             } else {
124592                 me.onLayout();
124593             }
124594
124595             newCard.fireEvent('activate', newCard, oldCard);
124596
124597             return newCard;
124598         }
124599         return false;
124600     },
124601
124602     configureItem: function(item) {
124603         // Card layout only controls dimensions which IT has controlled.
124604         // That calculation has to be determined at run time by examining the ownerCt's isFixedWidth()/isFixedHeight() methods
124605         item.layoutManagedHeight = 0;
124606         item.layoutManagedWidth = 0;
124607
124608         this.callParent(arguments);
124609     }});
124610 /**
124611  * This is the layout style of choice for creating structural layouts in a multi-column format where the width of each
124612  * column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content. This
124613  * class is intended to be extended or created via the layout:'column' {@link Ext.container.Container#layout} config,
124614  * and should generally not need to be created directly via the new keyword.
124615  *
124616  * ColumnLayout does not have any direct config options (other than inherited ones), but it does support a specific
124617  * config property of `columnWidth` that can be included in the config of any panel added to it. The layout will use
124618  * the columnWidth (if present) or width of each panel during layout to determine how to size each panel. If width or
124619  * columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).
124620  *
124621  * The width property is always evaluated as pixels, and must be a number greater than or equal to 1. The columnWidth
124622  * property is always evaluated as a percentage, and must be a decimal value greater than 0 and less than 1 (e.g., .25).
124623  *
124624  * The basic rules for specifying column widths are pretty simple. The logic makes two passes through the set of
124625  * contained panels. During the first layout pass, all panels that either have a fixed width or none specified (auto)
124626  * are skipped, but their widths are subtracted from the overall container width.
124627  *
124628  * During the second pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages
124629  * based on the total **remaining** container width. In other words, percentage width panels are designed to fill
124630  * the space left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any
124631  * number of columns with different percentages, the columnWidths must always add up to 1 (or 100%) when added
124632  * together, otherwise your layout may not render as expected.
124633  *
124634  *     @example
124635  *     // All columns are percentages -- they must add up to 1
124636  *     Ext.create('Ext.panel.Panel', {
124637  *         title: 'Column Layout - Percentage Only',
124638  *         width: 350,
124639  *         height: 250,
124640  *         layout:'column',
124641  *         items: [{
124642  *             title: 'Column 1',
124643  *             columnWidth: .25
124644  *         },{
124645  *             title: 'Column 2',
124646  *             columnWidth: .55
124647  *         },{
124648  *             title: 'Column 3',
124649  *             columnWidth: .20
124650  *         }],
124651  *         renderTo: Ext.getBody()
124652  *     });
124653  *
124654  *     // Mix of width and columnWidth -- all columnWidth values must add up
124655  *     // to 1. The first column will take up exactly 120px, and the last two
124656  *     // columns will fill the remaining container width.
124657  *
124658  *     Ext.create('Ext.Panel', {
124659  *         title: 'Column Layout - Mixed',
124660  *         width: 350,
124661  *         height: 250,
124662  *         layout:'column',
124663  *         items: [{
124664  *             title: 'Column 1',
124665  *             width: 120
124666  *         },{
124667  *             title: 'Column 2',
124668  *             columnWidth: .7
124669  *         },{
124670  *             title: 'Column 3',
124671  *             columnWidth: .3
124672  *         }],
124673  *         renderTo: Ext.getBody()
124674  *     });
124675  */
124676 Ext.define('Ext.layout.container.Column', {
124677
124678     extend: 'Ext.layout.container.Auto',
124679     alias: ['layout.column'],
124680     alternateClassName: 'Ext.layout.ColumnLayout',
124681
124682     type: 'column',
124683
124684     itemCls: Ext.baseCSSPrefix + 'column',
124685
124686     targetCls: Ext.baseCSSPrefix + 'column-layout-ct',
124687
124688     scrollOffset: 0,
124689
124690     bindToOwnerCtComponent: false,
124691
124692     getRenderTarget : function() {
124693         if (!this.innerCt) {
124694
124695             // the innerCt prevents wrapping and shuffling while
124696             // the container is resizing
124697             this.innerCt = this.getTarget().createChild({
124698                 cls: Ext.baseCSSPrefix + 'column-inner'
124699             });
124700
124701             // Column layout uses natural HTML flow to arrange the child items.
124702             // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
124703             // containing element height, we create a zero-sized element with style clear:both to force a "new line"
124704             this.clearEl = this.innerCt.createChild({
124705                 cls: Ext.baseCSSPrefix + 'clear',
124706                 role: 'presentation'
124707             });
124708         }
124709         return this.innerCt;
124710     },
124711
124712     // private
124713     onLayout : function() {
124714         var me = this,
124715             target = me.getTarget(),
124716             items = me.getLayoutItems(),
124717             len = items.length,
124718             item,
124719             i,
124720             parallelMargins = [],
124721             itemParallelMargins,
124722             size,
124723             availableWidth,
124724             columnWidth;
124725
124726         size = me.getLayoutTargetSize();
124727         if (size.width < len * 10) { // Don't lay out in impossibly small target (probably display:none, or initial, unsized Container)
124728             return;
124729         }
124730
124731         // On the first pass, for all except IE6-7, we lay out the items with no scrollbars visible using style overflow: hidden.
124732         // If, after the layout, it is detected that there is vertical overflow,
124733         // we will recurse back through here. Do not adjust overflow style at that time.
124734         if (me.adjustmentPass) {
124735             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
124736                 size.width = me.adjustedWidth;
124737             }
124738         } else {
124739             i = target.getStyle('overflow');
124740             if (i && i != 'hidden') {
124741                 me.autoScroll = true;
124742                 if (!(Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks)) {
124743                     target.setStyle('overflow', 'hidden');
124744                     size = me.getLayoutTargetSize();
124745                 }
124746             }
124747         }
124748
124749         availableWidth = size.width - me.scrollOffset;
124750         me.innerCt.setWidth(availableWidth);
124751
124752         // some columns can be percentages while others are fixed
124753         // so we need to make 2 passes
124754         for (i = 0; i < len; i++) {
124755             item = items[i];
124756             itemParallelMargins = parallelMargins[i] = item.getEl().getMargin('lr');
124757             if (!item.columnWidth) {
124758                 availableWidth -= (item.getWidth() + itemParallelMargins);
124759             }
124760         }
124761
124762         availableWidth = availableWidth < 0 ? 0 : availableWidth;
124763         for (i = 0; i < len; i++) {
124764             item = items[i];
124765             if (item.columnWidth) {
124766                 columnWidth = Math.floor(item.columnWidth * availableWidth) - parallelMargins[i];
124767                 me.setItemSize(item, columnWidth, item.height);
124768             } else {
124769                 me.layoutItem(item);
124770             }
124771         }
124772
124773         // After the first pass on an autoScroll layout, restore the overflow settings if it had been changed (only changed for non-IE6)
124774         if (!me.adjustmentPass && me.autoScroll) {
124775
124776             // If there's a vertical overflow, relay with scrollbars
124777             target.setStyle('overflow', 'auto');
124778             me.adjustmentPass = (target.dom.scrollHeight > size.height);
124779             if (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) {
124780                 me.adjustedWidth = size.width - Ext.getScrollBarWidth();
124781             } else {
124782                 target.setStyle('overflow', 'auto');
124783             }
124784
124785             // If the layout caused height overflow, recurse back and recalculate (with overflow setting restored on non-IE6)
124786             if (me.adjustmentPass) {
124787                 me.onLayout();
124788             }
124789         }
124790         delete me.adjustmentPass;
124791     },
124792
124793     configureItem: function(item) {
124794         this.callParent(arguments);
124795
124796         if (item.columnWidth) {
124797             item.layoutManagedWidth = 1;
124798         }
124799     }
124800 });
124801 /**
124802  * This layout allows you to easily render content into an HTML table. The total number of columns can be specified, and
124803  * rowspan and colspan can be used to create complex layouts within the table. This class is intended to be extended or
124804  * created via the `layout: {type: 'table'}` {@link Ext.container.Container#layout} config, and should generally not
124805  * need to be created directly via the new keyword.
124806  *
124807  * Note that when creating a layout via config, the layout-specific config properties must be passed in via the {@link
124808  * Ext.container.Container#layout} object which will then be applied internally to the layout. In the case of
124809  * TableLayout, the only valid layout config properties are {@link #columns} and {@link #tableAttrs}. However, the items
124810  * added to a TableLayout can supply the following table-specific config properties:
124811  *
124812  *   - **rowspan** Applied to the table cell containing the item.
124813  *   - **colspan** Applied to the table cell containing the item.
124814  *   - **cellId** An id applied to the table cell containing the item.
124815  *   - **cellCls** A CSS class name added to the table cell containing the item.
124816  *
124817  * The basic concept of building up a TableLayout is conceptually very similar to building up a standard HTML table. You
124818  * simply add each panel (or "cell") that you want to include along with any span attributes specified as the special
124819  * config properties of rowspan and colspan which work exactly like their HTML counterparts. Rather than explicitly
124820  * creating and nesting rows and columns as you would in HTML, you simply specify the total column count in the
124821  * layoutConfig and start adding panels in their natural order from left to right, top to bottom. The layout will
124822  * automatically figure out, based on the column count, rowspans and colspans, how to position each panel within the
124823  * table. Just like with HTML tables, your rowspans and colspans must add up correctly in your overall layout or you'll
124824  * end up with missing and/or extra cells! Example usage:
124825  *
124826  *     @example
124827  *     Ext.create('Ext.panel.Panel', {
124828  *         title: 'Table Layout',
124829  *         width: 300,
124830  *         height: 150,
124831  *         layout: {
124832  *             type: 'table',
124833  *             // The total column count must be specified here
124834  *             columns: 3
124835  *         },
124836  *         defaults: {
124837  *             // applied to each contained panel
124838  *             bodyStyle: 'padding:20px'
124839  *         },
124840  *         items: [{
124841  *             html: 'Cell A content',
124842  *             rowspan: 2
124843  *         },{
124844  *             html: 'Cell B content',
124845  *             colspan: 2
124846  *         },{
124847  *             html: 'Cell C content',
124848  *             cellCls: 'highlight'
124849  *         },{
124850  *             html: 'Cell D content'
124851  *         }],
124852  *         renderTo: Ext.getBody()
124853  *     });
124854  */
124855 Ext.define('Ext.layout.container.Table', {
124856
124857     /* Begin Definitions */
124858
124859     alias: ['layout.table'],
124860     extend: 'Ext.layout.container.Auto',
124861     alternateClassName: 'Ext.layout.TableLayout',
124862
124863     /* End Definitions */
124864
124865     /**
124866      * @cfg {Number} columns
124867      * The total number of columns to create in the table for this layout. If not specified, all Components added to
124868      * this layout will be rendered into a single row using one column per Component.
124869      */
124870
124871     // private
124872     monitorResize:false,
124873
124874     type: 'table',
124875
124876     // Table layout is a self-sizing layout. When an item of for example, a dock layout, the Panel must expand to accommodate
124877     // a table layout. See in particular AbstractDock::onLayout for use of this flag.
124878     autoSize: true,
124879
124880     clearEl: true, // Base class will not create it if already truthy. Not needed in tables.
124881
124882     targetCls: Ext.baseCSSPrefix + 'table-layout-ct',
124883     tableCls: Ext.baseCSSPrefix + 'table-layout',
124884     cellCls: Ext.baseCSSPrefix + 'table-layout-cell',
124885
124886     /**
124887      * @cfg {Object} tableAttrs
124888      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
124889      * create the layout's `<table>` element. Example:
124890      *
124891      *     {
124892      *         xtype: 'panel',
124893      *         layout: {
124894      *             type: 'table',
124895      *             columns: 3,
124896      *             tableAttrs: {
124897      *                 style: {
124898      *                     width: '100%'
124899      *                 }
124900      *             }
124901      *         }
124902      *     }
124903      */
124904     tableAttrs:null,
124905
124906     /**
124907      * @cfg {Object} trAttrs
124908      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
124909      * create the layout's <tr> elements.
124910      */
124911
124912     /**
124913      * @cfg {Object} tdAttrs
124914      * An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification used to
124915      * create the layout's <td> elements.
124916      */
124917
124918     /**
124919      * @private
124920      * Iterates over all passed items, ensuring they are rendered in a cell in the proper
124921      * location in the table structure.
124922      */
124923     renderItems: function(items) {
124924         var tbody = this.getTable().tBodies[0],
124925             rows = tbody.rows,
124926             i = 0,
124927             len = items.length,
124928             cells, curCell, rowIdx, cellIdx, item, trEl, tdEl, itemCt;
124929
124930         // Calculate the correct cell structure for the current items
124931         cells = this.calculateCells(items);
124932
124933         // Loop over each cell and compare to the current cells in the table, inserting/
124934         // removing/moving cells as needed, and making sure each item is rendered into
124935         // the correct cell.
124936         for (; i < len; i++) {
124937             curCell = cells[i];
124938             rowIdx = curCell.rowIdx;
124939             cellIdx = curCell.cellIdx;
124940             item = items[i];
124941
124942             // If no row present, create and insert one
124943             trEl = rows[rowIdx];
124944             if (!trEl) {
124945                 trEl = tbody.insertRow(rowIdx);
124946                 if (this.trAttrs) {
124947                     trEl.set(this.trAttrs);
124948                 }
124949             }
124950
124951             // If no cell present, create and insert one
124952             itemCt = tdEl = Ext.get(trEl.cells[cellIdx] || trEl.insertCell(cellIdx));
124953             if (this.needsDivWrap()) { //create wrapper div if needed - see docs below
124954                 itemCt = tdEl.first() || tdEl.createChild({tag: 'div'});
124955                 itemCt.setWidth(null);
124956             }
124957
124958             // Render or move the component into the cell
124959             if (!item.rendered) {
124960                 this.renderItem(item, itemCt, 0);
124961             }
124962             else if (!this.isValidParent(item, itemCt, 0)) {
124963                 this.moveItem(item, itemCt, 0);
124964             }
124965
124966             // Set the cell properties
124967             if (this.tdAttrs) {
124968                 tdEl.set(this.tdAttrs);
124969             }
124970             tdEl.set({
124971                 colSpan: item.colspan || 1,
124972                 rowSpan: item.rowspan || 1,
124973                 id: item.cellId || '',
124974                 cls: this.cellCls + ' ' + (item.cellCls || '')
124975             });
124976
124977             // If at the end of a row, remove any extra cells
124978             if (!cells[i + 1] || cells[i + 1].rowIdx !== rowIdx) {
124979                 cellIdx++;
124980                 while (trEl.cells[cellIdx]) {
124981                     trEl.deleteCell(cellIdx);
124982                 }
124983             }
124984         }
124985
124986         // Delete any extra rows
124987         rowIdx++;
124988         while (tbody.rows[rowIdx]) {
124989             tbody.deleteRow(rowIdx);
124990         }
124991     },
124992
124993     afterLayout: function() {
124994         this.callParent();
124995
124996         if (this.needsDivWrap()) {
124997             // set wrapper div width to match layed out item - see docs below
124998             Ext.Array.forEach(this.getLayoutItems(), function(item) {
124999                 Ext.fly(item.el.dom.parentNode).setWidth(item.getWidth());
125000             });
125001         }
125002     },
125003
125004     /**
125005      * @private
125006      * Determine the row and cell indexes for each component, taking into consideration
125007      * the number of columns and each item's configured colspan/rowspan values.
125008      * @param {Array} items The layout components
125009      * @return {Object[]} List of row and cell indexes for each of the components
125010      */
125011     calculateCells: function(items) {
125012         var cells = [],
125013             rowIdx = 0,
125014             colIdx = 0,
125015             cellIdx = 0,
125016             totalCols = this.columns || Infinity,
125017             rowspans = [], //rolling list of active rowspans for each column
125018             i = 0, j,
125019             len = items.length,
125020             item;
125021
125022         for (; i < len; i++) {
125023             item = items[i];
125024
125025             // Find the first available row/col slot not taken up by a spanning cell
125026             while (colIdx >= totalCols || rowspans[colIdx] > 0) {
125027                 if (colIdx >= totalCols) {
125028                     // move down to next row
125029                     colIdx = 0;
125030                     cellIdx = 0;
125031                     rowIdx++;
125032
125033                     // decrement all rowspans
125034                     for (j = 0; j < totalCols; j++) {
125035                         if (rowspans[j] > 0) {
125036                             rowspans[j]--;
125037                         }
125038                     }
125039                 } else {
125040                     colIdx++;
125041                 }
125042             }
125043
125044             // Add the cell info to the list
125045             cells.push({
125046                 rowIdx: rowIdx,
125047                 cellIdx: cellIdx
125048             });
125049
125050             // Increment
125051             for (j = item.colspan || 1; j; --j) {
125052                 rowspans[colIdx] = item.rowspan || 1;
125053                 ++colIdx;
125054             }
125055             ++cellIdx;
125056         }
125057
125058         return cells;
125059     },
125060
125061     /**
125062      * @private
125063      * Return the layout's table element, creating it if necessary.
125064      */
125065     getTable: function() {
125066         var table = this.table;
125067         if (!table) {
125068             table = this.table = this.getTarget().createChild(
125069                 Ext.apply({
125070                     tag: 'table',
125071                     role: 'presentation',
125072                     cls: this.tableCls,
125073                     cellspacing: 0, //TODO should this be specified or should CSS handle it?
125074                     cn: {tag: 'tbody'}
125075                 }, this.tableAttrs),
125076                 null, true
125077             );
125078         }
125079         return table;
125080     },
125081
125082     /**
125083      * @private
125084      * Opera 10.5 has a bug where if a table cell's child has box-sizing:border-box and padding, it
125085      * will include that padding in the size of the cell, making it always larger than the
125086      * shrink-wrapped size of its contents. To get around this we have to wrap the contents in a div
125087      * and then set that div's width to match the item rendered within it afterLayout. This method
125088      * determines whether we need the wrapper div; it currently does a straight UA sniff as this bug
125089      * seems isolated to just Opera 10.5, but feature detection could be added here if needed.
125090      */
125091     needsDivWrap: function() {
125092         return Ext.isOpera10_5;
125093     }
125094 });
125095 /**
125096  * A base class for all menu items that require menu-related functionality such as click handling,
125097  * sub-menus, icons, etc.
125098  *
125099  *     @example
125100  *     Ext.create('Ext.menu.Menu', {
125101  *         width: 100,
125102  *         height: 100,
125103  *         floating: false,  // usually you want this set to True (default)
125104  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125105  *         items: [{
125106  *             text: 'icon item',
125107  *             iconCls: 'add16'
125108  *         },{
125109  *             text: 'text item'
125110  *         },{
125111  *             text: 'plain item',
125112  *             plain: true
125113  *         }]
125114  *     });
125115  */
125116 Ext.define('Ext.menu.Item', {
125117     extend: 'Ext.Component',
125118     alias: 'widget.menuitem',
125119     alternateClassName: 'Ext.menu.TextItem',
125120
125121     /**
125122      * @property {Boolean} activated
125123      * Whether or not this item is currently activated
125124      */
125125
125126     /**
125127      * @property {Ext.menu.Menu} parentMenu
125128      * The parent Menu of this item.
125129      */
125130
125131     /**
125132      * @cfg {String} activeCls
125133      * The CSS class added to the menu item when the item is activated (focused/mouseover).
125134      * Defaults to `Ext.baseCSSPrefix + 'menu-item-active'`.
125135      */
125136     activeCls: Ext.baseCSSPrefix + 'menu-item-active',
125137
125138     /**
125139      * @cfg {String} ariaRole @hide
125140      */
125141     ariaRole: 'menuitem',
125142
125143     /**
125144      * @cfg {Boolean} canActivate
125145      * Whether or not this menu item can be activated when focused/mouseovered. Defaults to `true`.
125146      */
125147     canActivate: true,
125148
125149     /**
125150      * @cfg {Number} clickHideDelay
125151      * The delay in milliseconds to wait before hiding the menu after clicking the menu item.
125152      * This only has an effect when `hideOnClick: true`. Defaults to `1`.
125153      */
125154     clickHideDelay: 1,
125155
125156     /**
125157      * @cfg {Boolean} destroyMenu
125158      * Whether or not to destroy any associated sub-menu when this item is destroyed. Defaults to `true`.
125159      */
125160     destroyMenu: true,
125161
125162     /**
125163      * @cfg {String} disabledCls
125164      * The CSS class added to the menu item when the item is disabled.
125165      * Defaults to `Ext.baseCSSPrefix + 'menu-item-disabled'`.
125166      */
125167     disabledCls: Ext.baseCSSPrefix + 'menu-item-disabled',
125168
125169     /**
125170      * @cfg {String} href
125171      * The href attribute to use for the underlying anchor link. Defaults to `#`.
125172      * @markdown
125173      */
125174
125175      /**
125176       * @cfg {String} hrefTarget
125177       * The target attribute to use for the underlying anchor link. Defaults to `undefined`.
125178       * @markdown
125179       */
125180
125181     /**
125182      * @cfg {Boolean} hideOnClick
125183      * Whether to not to hide the owning menu when this item is clicked. Defaults to `true`.
125184      * @markdown
125185      */
125186     hideOnClick: true,
125187
125188     /**
125189      * @cfg {String} icon
125190      * The path to an icon to display in this item. Defaults to `Ext.BLANK_IMAGE_URL`.
125191      * @markdown
125192      */
125193
125194     /**
125195      * @cfg {String} iconCls
125196      * A CSS class that specifies a `background-image` to use as the icon for this item. Defaults to `undefined`.
125197      * @markdown
125198      */
125199
125200     isMenuItem: true,
125201
125202     /**
125203      * @cfg {Mixed} menu
125204      * Either an instance of {@link Ext.menu.Menu} or a config object for an {@link Ext.menu.Menu}
125205      * which will act as a sub-menu to this item.
125206      * @markdown
125207      * @property {Ext.menu.Menu} menu The sub-menu associated with this item, if one was configured.
125208      */
125209
125210     /**
125211      * @cfg {String} menuAlign
125212      * The default {@link Ext.Element#getAlignToXY Ext.Element.getAlignToXY} anchor position value for this
125213      * item's sub-menu relative to this item's position. Defaults to `'tl-tr?'`.
125214      * @markdown
125215      */
125216     menuAlign: 'tl-tr?',
125217
125218     /**
125219      * @cfg {Number} menuExpandDelay
125220      * The delay in milliseconds before this item's sub-menu expands after this item is moused over. Defaults to `200`.
125221      * @markdown
125222      */
125223     menuExpandDelay: 200,
125224
125225     /**
125226      * @cfg {Number} menuHideDelay
125227      * The delay in milliseconds before this item's sub-menu hides after this item is moused out. Defaults to `200`.
125228      * @markdown
125229      */
125230     menuHideDelay: 200,
125231
125232     /**
125233      * @cfg {Boolean} plain
125234      * Whether or not this item is plain text/html with no icon or visual activation. Defaults to `false`.
125235      * @markdown
125236      */
125237
125238     renderTpl: [
125239         '<tpl if="plain">',
125240             '{text}',
125241         '</tpl>',
125242         '<tpl if="!plain">',
125243             '<a id="{id}-itemEl" class="' + Ext.baseCSSPrefix + 'menu-item-link" href="{href}" <tpl if="hrefTarget">target="{hrefTarget}"</tpl> hidefocus="true" unselectable="on">',
125244                 '<img id="{id}-iconEl" src="{icon}" class="' + Ext.baseCSSPrefix + 'menu-item-icon {iconCls}" />',
125245                 '<span id="{id}-textEl" class="' + Ext.baseCSSPrefix + 'menu-item-text" <tpl if="menu">style="margin-right: 17px;"</tpl> >{text}</span>',
125246                 '<tpl if="menu">',
125247                     '<img id="{id}-arrowEl" src="{blank}" class="' + Ext.baseCSSPrefix + 'menu-item-arrow" />',
125248                 '</tpl>',
125249             '</a>',
125250         '</tpl>'
125251     ],
125252
125253     maskOnDisable: false,
125254
125255     /**
125256      * @cfg {String} text
125257      * The text/html to display in this item. Defaults to `undefined`.
125258      * @markdown
125259      */
125260
125261     activate: function() {
125262         var me = this;
125263
125264         if (!me.activated && me.canActivate && me.rendered && !me.isDisabled() && me.isVisible()) {
125265             me.el.addCls(me.activeCls);
125266             me.focus();
125267             me.activated = true;
125268             me.fireEvent('activate', me);
125269         }
125270     },
125271
125272     blur: function() {
125273         this.$focused = false;
125274         this.callParent(arguments);
125275     },
125276
125277     deactivate: function() {
125278         var me = this;
125279
125280         if (me.activated) {
125281             me.el.removeCls(me.activeCls);
125282             me.blur();
125283             me.hideMenu();
125284             me.activated = false;
125285             me.fireEvent('deactivate', me);
125286         }
125287     },
125288
125289     deferExpandMenu: function() {
125290         var me = this;
125291
125292         if (!me.menu.rendered || !me.menu.isVisible()) {
125293             me.parentMenu.activeChild = me.menu;
125294             me.menu.parentItem = me;
125295             me.menu.parentMenu = me.menu.ownerCt = me.parentMenu;
125296             me.menu.showBy(me, me.menuAlign);
125297         }
125298     },
125299
125300     deferHideMenu: function() {
125301         if (this.menu.isVisible()) {
125302             this.menu.hide();
125303         }
125304     },
125305
125306     deferHideParentMenus: function() {
125307         Ext.menu.Manager.hideAll();
125308     },
125309
125310     expandMenu: function(delay) {
125311         var me = this;
125312
125313         if (me.menu) {
125314             clearTimeout(me.hideMenuTimer);
125315             if (delay === 0) {
125316                 me.deferExpandMenu();
125317             } else {
125318                 me.expandMenuTimer = Ext.defer(me.deferExpandMenu, Ext.isNumber(delay) ? delay : me.menuExpandDelay, me);
125319             }
125320         }
125321     },
125322
125323     focus: function() {
125324         this.$focused = true;
125325         this.callParent(arguments);
125326     },
125327
125328     getRefItems: function(deep){
125329         var menu = this.menu,
125330             items;
125331
125332         if (menu) {
125333             items = menu.getRefItems(deep);
125334             items.unshift(menu);
125335         }
125336         return items || [];
125337     },
125338
125339     hideMenu: function(delay) {
125340         var me = this;
125341
125342         if (me.menu) {
125343             clearTimeout(me.expandMenuTimer);
125344             me.hideMenuTimer = Ext.defer(me.deferHideMenu, Ext.isNumber(delay) ? delay : me.menuHideDelay, me);
125345         }
125346     },
125347
125348     initComponent: function() {
125349         var me = this,
125350             prefix = Ext.baseCSSPrefix,
125351             cls = [prefix + 'menu-item'];
125352
125353         me.addEvents(
125354             /**
125355              * @event activate
125356              * Fires when this item is activated
125357              * @param {Ext.menu.Item} item The activated item
125358              */
125359             'activate',
125360
125361             /**
125362              * @event click
125363              * Fires when this item is clicked
125364              * @param {Ext.menu.Item} item The item that was clicked
125365              * @param {Ext.EventObject} e The underyling {@link Ext.EventObject}.
125366              */
125367             'click',
125368
125369             /**
125370              * @event deactivate
125371              * Fires when this tiem is deactivated
125372              * @param {Ext.menu.Item} item The deactivated item
125373              */
125374             'deactivate'
125375         );
125376
125377         if (me.plain) {
125378             cls.push(prefix + 'menu-item-plain');
125379         }
125380
125381         if (me.cls) {
125382             cls.push(me.cls);
125383         }
125384
125385         me.cls = cls.join(' ');
125386
125387         if (me.menu) {
125388             me.menu = Ext.menu.Manager.get(me.menu);
125389         }
125390
125391         me.callParent(arguments);
125392     },
125393
125394     onClick: function(e) {
125395         var me = this;
125396
125397         if (!me.href) {
125398             e.stopEvent();
125399         }
125400
125401         if (me.disabled) {
125402             return;
125403         }
125404
125405         if (me.hideOnClick) {
125406             me.deferHideParentMenusTimer = Ext.defer(me.deferHideParentMenus, me.clickHideDelay, me);
125407         }
125408
125409         Ext.callback(me.handler, me.scope || me, [me, e]);
125410         me.fireEvent('click', me, e);
125411
125412         if (!me.hideOnClick) {
125413             me.focus();
125414         }
125415     },
125416
125417     onDestroy: function() {
125418         var me = this;
125419
125420         clearTimeout(me.expandMenuTimer);
125421         clearTimeout(me.hideMenuTimer);
125422         clearTimeout(me.deferHideParentMenusTimer);
125423
125424         if (me.menu) {
125425             delete me.menu.parentItem;
125426             delete me.menu.parentMenu;
125427             delete me.menu.ownerCt;
125428             if (me.destroyMenu !== false) {
125429                 me.menu.destroy();
125430             }
125431         }
125432         me.callParent(arguments);
125433     },
125434
125435     onRender: function(ct, pos) {
125436         var me = this,
125437             blank = Ext.BLANK_IMAGE_URL;
125438
125439         Ext.applyIf(me.renderData, {
125440             href: me.href || '#',
125441             hrefTarget: me.hrefTarget,
125442             icon: me.icon || blank,
125443             iconCls: me.iconCls + (me.checkChangeDisabled ? ' ' + me.disabledCls : ''),
125444             menu: Ext.isDefined(me.menu),
125445             plain: me.plain,
125446             text: me.text,
125447             blank: blank
125448         });
125449
125450         me.addChildEls('itemEl', 'iconEl', 'textEl', 'arrowEl');
125451
125452         me.callParent(arguments);
125453     },
125454
125455     /**
125456      * Sets the {@link #click} handler of this item
125457      * @param {Function} fn The handler function
125458      * @param {Object} scope (optional) The scope of the handler function
125459      */
125460     setHandler: function(fn, scope) {
125461         this.handler = fn || null;
125462         this.scope = scope;
125463     },
125464
125465     /**
125466      * Sets the {@link #iconCls} of this item
125467      * @param {String} iconCls The CSS class to set to {@link #iconCls}
125468      */
125469     setIconCls: function(iconCls) {
125470         var me = this;
125471
125472         if (me.iconEl) {
125473             if (me.iconCls) {
125474                 me.iconEl.removeCls(me.iconCls);
125475             }
125476
125477             if (iconCls) {
125478                 me.iconEl.addCls(iconCls);
125479             }
125480         }
125481
125482         me.iconCls = iconCls;
125483     },
125484
125485     /**
125486      * Sets the {@link #text} of this item
125487      * @param {String} text The {@link #text}
125488      */
125489     setText: function(text) {
125490         var me = this,
125491             el = me.textEl || me.el;
125492
125493         me.text = text;
125494
125495         if (me.rendered) {
125496             el.update(text || '');
125497             // cannot just call doComponentLayout due to stretchmax
125498             me.ownerCt.redoComponentLayout();
125499         }
125500     }
125501 });
125502
125503 /**
125504  * A menu item that contains a togglable checkbox by default, but that can also be a part of a radio group.
125505  *
125506  *     @example
125507  *     Ext.create('Ext.menu.Menu', {
125508  *         width: 100,
125509  *         height: 110,
125510  *         floating: false,  // usually you want this set to True (default)
125511  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125512  *         items: [{
125513  *             xtype: 'menucheckitem',
125514  *             text: 'select all'
125515  *         },{
125516  *             xtype: 'menucheckitem',
125517  *             text: 'select specific',
125518  *         },{
125519  *             iconCls: 'add16',
125520  *             text: 'icon item'
125521  *         },{
125522  *             text: 'regular item'
125523  *         }]
125524  *     });
125525  */
125526 Ext.define('Ext.menu.CheckItem', {
125527     extend: 'Ext.menu.Item',
125528     alias: 'widget.menucheckitem',
125529
125530     /**
125531      * @cfg {String} checkedCls
125532      * The CSS class used by {@link #cls} to show the checked state.
125533      * Defaults to `Ext.baseCSSPrefix + 'menu-item-checked'`.
125534      */
125535     checkedCls: Ext.baseCSSPrefix + 'menu-item-checked',
125536     /**
125537      * @cfg {String} uncheckedCls
125538      * The CSS class used by {@link #cls} to show the unchecked state.
125539      * Defaults to `Ext.baseCSSPrefix + 'menu-item-unchecked'`.
125540      */
125541     uncheckedCls: Ext.baseCSSPrefix + 'menu-item-unchecked',
125542     /**
125543      * @cfg {String} groupCls
125544      * The CSS class applied to this item's icon image to denote being a part of a radio group.
125545      * Defaults to `Ext.baseCSSClass + 'menu-group-icon'`.
125546      * Any specified {@link #iconCls} overrides this.
125547      */
125548     groupCls: Ext.baseCSSPrefix + 'menu-group-icon',
125549
125550     /**
125551      * @cfg {Boolean} hideOnClick
125552      * Whether to not to hide the owning menu when this item is clicked.
125553      * Defaults to `false` for checkbox items, and to `true` for radio group items.
125554      */
125555     hideOnClick: false,
125556
125557     afterRender: function() {
125558         var me = this;
125559         this.callParent();
125560         me.checked = !me.checked;
125561         me.setChecked(!me.checked, true);
125562     },
125563
125564     initComponent: function() {
125565         var me = this;
125566         me.addEvents(
125567             /**
125568              * @event beforecheckchange
125569              * Fires before a change event. Return false to cancel.
125570              * @param {Ext.menu.CheckItem} this
125571              * @param {Boolean} checked
125572              */
125573             'beforecheckchange',
125574
125575             /**
125576              * @event checkchange
125577              * Fires after a change event.
125578              * @param {Ext.menu.CheckItem} this
125579              * @param {Boolean} checked
125580              */
125581             'checkchange'
125582         );
125583
125584         me.callParent(arguments);
125585
125586         Ext.menu.Manager.registerCheckable(me);
125587
125588         if (me.group) {
125589             if (!me.iconCls) {
125590                 me.iconCls = me.groupCls;
125591             }
125592             if (me.initialConfig.hideOnClick !== false) {
125593                 me.hideOnClick = true;
125594             }
125595         }
125596     },
125597
125598     /**
125599      * Disables just the checkbox functionality of this menu Item. If this menu item has a submenu, that submenu
125600      * will still be accessible
125601      */
125602     disableCheckChange: function() {
125603         var me = this;
125604
125605         if (me.iconEl) {
125606             me.iconEl.addCls(me.disabledCls);
125607         }
125608         me.checkChangeDisabled = true;
125609     },
125610
125611     /**
125612      * Reenables the checkbox functionality of this menu item after having been disabled by {@link #disableCheckChange}
125613      */
125614     enableCheckChange: function() {
125615         var me = this;
125616
125617         me.iconEl.removeCls(me.disabledCls);
125618         me.checkChangeDisabled = false;
125619     },
125620
125621     onClick: function(e) {
125622         var me = this;
125623         if(!me.disabled && !me.checkChangeDisabled && !(me.checked && me.group)) {
125624             me.setChecked(!me.checked);
125625         }
125626         this.callParent([e]);
125627     },
125628
125629     onDestroy: function() {
125630         Ext.menu.Manager.unregisterCheckable(this);
125631         this.callParent(arguments);
125632     },
125633
125634     /**
125635      * Sets the checked state of the item
125636      * @param {Boolean} checked True to check, false to uncheck
125637      * @param {Boolean} suppressEvents (optional) True to prevent firing the checkchange events. Defaults to `false`.
125638      */
125639     setChecked: function(checked, suppressEvents) {
125640         var me = this;
125641         if (me.checked !== checked && (suppressEvents || me.fireEvent('beforecheckchange', me, checked) !== false)) {
125642             if (me.el) {
125643                 me.el[checked  ? 'addCls' : 'removeCls'](me.checkedCls)[!checked ? 'addCls' : 'removeCls'](me.uncheckedCls);
125644             }
125645             me.checked = checked;
125646             Ext.menu.Manager.onCheckChange(me, checked);
125647             if (!suppressEvents) {
125648                 Ext.callback(me.checkHandler, me.scope, [me, checked]);
125649                 me.fireEvent('checkchange', me, checked);
125650             }
125651         }
125652     }
125653 });
125654
125655 /**
125656  * @class Ext.menu.KeyNav
125657  * @private
125658  */
125659 Ext.define('Ext.menu.KeyNav', {
125660     extend: 'Ext.util.KeyNav',
125661
125662     requires: ['Ext.FocusManager'],
125663     
125664     constructor: function(menu) {
125665         var me = this;
125666
125667         me.menu = menu;
125668         me.callParent([menu.el, {
125669             down: me.down,
125670             enter: me.enter,
125671             esc: me.escape,
125672             left: me.left,
125673             right: me.right,
125674             space: me.enter,
125675             tab: me.tab,
125676             up: me.up
125677         }]);
125678     },
125679
125680     down: function(e) {
125681         var me = this,
125682             fi = me.menu.focusedItem;
125683
125684         if (fi && e.getKey() == Ext.EventObject.DOWN && me.isWhitelisted(fi)) {
125685             return true;
125686         }
125687         me.focusNextItem(1);
125688     },
125689
125690     enter: function(e) {
125691         var menu = this.menu,
125692             focused = menu.focusedItem;
125693  
125694         if (menu.activeItem) {
125695             menu.onClick(e);
125696         } else if (focused && focused.isFormField) {
125697             // prevent stopEvent being called
125698             return true;
125699         }
125700     },
125701
125702     escape: function(e) {
125703         Ext.menu.Manager.hideAll();
125704     },
125705
125706     focusNextItem: function(step) {
125707         var menu = this.menu,
125708             items = menu.items,
125709             focusedItem = menu.focusedItem,
125710             startIdx = focusedItem ? items.indexOf(focusedItem) : -1,
125711             idx = startIdx + step;
125712
125713         while (idx != startIdx) {
125714             if (idx < 0) {
125715                 idx = items.length - 1;
125716             } else if (idx >= items.length) {
125717                 idx = 0;
125718             }
125719
125720             var item = items.getAt(idx);
125721             if (menu.canActivateItem(item)) {
125722                 menu.setActiveItem(item);
125723                 break;
125724             }
125725             idx += step;
125726         }
125727     },
125728
125729     isWhitelisted: function(item) {
125730         return Ext.FocusManager.isWhitelisted(item);
125731     },
125732
125733     left: function(e) {
125734         var menu = this.menu,
125735             fi = menu.focusedItem,
125736             ai = menu.activeItem;
125737
125738         if (fi && this.isWhitelisted(fi)) {
125739             return true;
125740         }
125741
125742         menu.hide();
125743         if (menu.parentMenu) {
125744             menu.parentMenu.focus();
125745         }
125746     },
125747
125748     right: function(e) {
125749         var menu = this.menu,
125750             fi = menu.focusedItem,
125751             ai = menu.activeItem,
125752             am;
125753
125754         if (fi && this.isWhitelisted(fi)) {
125755             return true;
125756         }
125757
125758         if (ai) {
125759             am = menu.activeItem.menu;
125760             if (am) {
125761                 ai.expandMenu(0);
125762                 Ext.defer(function() {
125763                     am.setActiveItem(am.items.getAt(0));
125764                 }, 25);
125765             }
125766         }
125767     },
125768
125769     tab: function(e) {
125770         var me = this;
125771
125772         if (e.shiftKey) {
125773             me.up(e);
125774         } else {
125775             me.down(e);
125776         }
125777     },
125778
125779     up: function(e) {
125780         var me = this,
125781             fi = me.menu.focusedItem;
125782
125783         if (fi && e.getKey() == Ext.EventObject.UP && me.isWhitelisted(fi)) {
125784             return true;
125785         }
125786         me.focusNextItem(-1);
125787     }
125788 });
125789 /**
125790  * Adds a separator bar to a menu, used to divide logical groups of menu items. Generally you will
125791  * add one of these by using "-" in your call to add() or in your items config rather than creating one directly.
125792  *
125793  *     @example
125794  *     Ext.create('Ext.menu.Menu', {
125795  *         width: 100,
125796  *         height: 100,
125797  *         floating: false,  // usually you want this set to True (default)
125798  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125799  *         items: [{
125800  *             text: 'icon item',
125801  *             iconCls: 'add16'
125802  *         },{
125803  *             xtype: 'menuseparator'
125804  *         },{
125805  *            text: 'seperator above',
125806  *         },{
125807  *            text: 'regular item',
125808  *         }]
125809  *     });
125810  */
125811 Ext.define('Ext.menu.Separator', {
125812     extend: 'Ext.menu.Item',
125813     alias: 'widget.menuseparator',
125814
125815     /**
125816      * @cfg {String} activeCls @hide
125817      */
125818
125819     /**
125820      * @cfg {Boolean} canActivate @hide
125821      */
125822     canActivate: false,
125823
125824     /**
125825      * @cfg {Boolean} clickHideDelay @hide
125826      */
125827
125828     /**
125829      * @cfg {Boolean} destroyMenu @hide
125830      */
125831
125832     /**
125833      * @cfg {Boolean} disabledCls @hide
125834      */
125835
125836     focusable: false,
125837
125838     /**
125839      * @cfg {String} href @hide
125840      */
125841
125842     /**
125843      * @cfg {String} hrefTarget @hide
125844      */
125845
125846     /**
125847      * @cfg {Boolean} hideOnClick @hide
125848      */
125849     hideOnClick: false,
125850
125851     /**
125852      * @cfg {String} icon @hide
125853      */
125854
125855     /**
125856      * @cfg {String} iconCls @hide
125857      */
125858
125859     /**
125860      * @cfg {Object} menu @hide
125861      */
125862
125863     /**
125864      * @cfg {String} menuAlign @hide
125865      */
125866
125867     /**
125868      * @cfg {Number} menuExpandDelay @hide
125869      */
125870
125871     /**
125872      * @cfg {Number} menuHideDelay @hide
125873      */
125874
125875     /**
125876      * @cfg {Boolean} plain @hide
125877      */
125878     plain: true,
125879
125880     /**
125881      * @cfg {String} separatorCls
125882      * The CSS class used by the separator item to show the incised line.
125883      * Defaults to `Ext.baseCSSPrefix + 'menu-item-separator'`.
125884      */
125885     separatorCls: Ext.baseCSSPrefix + 'menu-item-separator',
125886
125887     /**
125888      * @cfg {String} text @hide
125889      */
125890     text: '&#160;',
125891
125892     onRender: function(ct, pos) {
125893         var me = this,
125894             sepCls = me.separatorCls;
125895
125896         me.cls += ' ' + sepCls;
125897
125898         me.callParent(arguments);
125899     }
125900 });
125901 /**
125902  * A menu object. This is the container to which you may add {@link Ext.menu.Item menu items}.
125903  *
125904  * Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Components}.
125905  * Menus may also contain {@link Ext.panel.AbstractPanel#dockedItems docked items} because it extends {@link Ext.panel.Panel}.
125906  *
125907  * To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items},
125908  * specify `{@link Ext.menu.Item#plain plain}: true`. This reserves a space for an icon, and indents the Component
125909  * in line with the other menu items.
125910  *
125911  * By default, Menus are absolutely positioned, floating Components. By configuring a Menu with `{@link #floating}: false`,
125912  * a Menu may be used as a child of a {@link Ext.container.Container Container}.
125913  *
125914  *     @example
125915  *     Ext.create('Ext.menu.Menu', {
125916  *         width: 100,
125917  *         height: 100,
125918  *         margin: '0 0 10 0',
125919  *         floating: false,  // usually you want this set to True (default)
125920  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125921  *         items: [{
125922  *             text: 'regular item 1'
125923  *         },{
125924  *             text: 'regular item 2'
125925  *         },{
125926  *             text: 'regular item 3'
125927  *         }]
125928  *     });
125929  *
125930  *     Ext.create('Ext.menu.Menu', {
125931  *         width: 100,
125932  *         height: 100,
125933  *         plain: true,
125934  *         floating: false,  // usually you want this set to True (default)
125935  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
125936  *         items: [{
125937  *             text: 'plain item 1'
125938  *         },{
125939  *             text: 'plain item 2'
125940  *         },{
125941  *             text: 'plain item 3'
125942  *         }]
125943  *     });
125944  */
125945 Ext.define('Ext.menu.Menu', {
125946     extend: 'Ext.panel.Panel',
125947     alias: 'widget.menu',
125948     requires: [
125949         'Ext.layout.container.Fit',
125950         'Ext.layout.container.VBox',
125951         'Ext.menu.CheckItem',
125952         'Ext.menu.Item',
125953         'Ext.menu.KeyNav',
125954         'Ext.menu.Manager',
125955         'Ext.menu.Separator'
125956     ],
125957
125958     /**
125959      * @property {Ext.menu.Menu} parentMenu
125960      * The parent Menu of this Menu.
125961      */
125962
125963     /**
125964      * @cfg {Boolean} allowOtherMenus
125965      * True to allow multiple menus to be displayed at the same time.
125966      */
125967     allowOtherMenus: false,
125968
125969     /**
125970      * @cfg {String} ariaRole @hide
125971      */
125972     ariaRole: 'menu',
125973
125974     /**
125975      * @cfg {Boolean} autoRender @hide
125976      * floating is true, so autoRender always happens
125977      */
125978
125979     /**
125980      * @cfg {String} defaultAlign
125981      * The default {@link Ext.Element#getAlignToXY Ext.Element#getAlignToXY} anchor position value for this menu
125982      * relative to its element of origin.
125983      */
125984     defaultAlign: 'tl-bl?',
125985
125986     /**
125987      * @cfg {Boolean} floating
125988      * A Menu configured as `floating: true` (the default) will be rendered as an absolutely positioned,
125989      * {@link Ext.Component#floating floating} {@link Ext.Component Component}. If configured as `floating: false`, the Menu may be
125990      * used as a child item of another {@link Ext.container.Container Container}.
125991      */
125992     floating: true,
125993
125994     /**
125995      * @cfg {Boolean} @hide
125996      * Menus are constrained to the document body by default
125997      */
125998     constrain: true,
125999
126000     /**
126001      * @cfg {Boolean} [hidden=undefined]
126002      * True to initially render the Menu as hidden, requiring to be shown manually.
126003      *
126004      * Defaults to `true` when `floating: true`, and defaults to `false` when `floating: false`.
126005      */
126006     hidden: true,
126007
126008     hideMode: 'visibility',
126009
126010     /**
126011      * @cfg {Boolean} ignoreParentClicks
126012      * True to ignore clicks on any item in this menu that is a parent item (displays a submenu)
126013      * so that the submenu is not dismissed when clicking the parent item.
126014      */
126015     ignoreParentClicks: false,
126016
126017     isMenu: true,
126018
126019     /**
126020      * @cfg {String/Object} layout @hide
126021      */
126022
126023     /**
126024      * @cfg {Boolean} showSeparator
126025      * True to show the icon separator.
126026      */
126027     showSeparator : true,
126028
126029     /**
126030      * @cfg {Number} minWidth
126031      * The minimum width of the Menu.
126032      */
126033     minWidth: 120,
126034
126035     /**
126036      * @cfg {Boolean} [plain=false]
126037      * True to remove the incised line down the left side of the menu and to not indent general Component items.
126038      */
126039
126040     initComponent: function() {
126041         var me = this,
126042             prefix = Ext.baseCSSPrefix,
126043             cls = [prefix + 'menu'],
126044             bodyCls = me.bodyCls ? [me.bodyCls] : [];
126045
126046         me.addEvents(
126047             /**
126048              * @event click
126049              * Fires when this menu is clicked
126050              * @param {Ext.menu.Menu} menu The menu which has been clicked
126051              * @param {Ext.Component} item The menu item that was clicked. `undefined` if not applicable.
126052              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}.
126053              */
126054             'click',
126055
126056             /**
126057              * @event mouseenter
126058              * Fires when the mouse enters this menu
126059              * @param {Ext.menu.Menu} menu The menu
126060              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126061              */
126062             'mouseenter',
126063
126064             /**
126065              * @event mouseleave
126066              * Fires when the mouse leaves this menu
126067              * @param {Ext.menu.Menu} menu The menu
126068              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126069              */
126070             'mouseleave',
126071
126072             /**
126073              * @event mouseover
126074              * Fires when the mouse is hovering over this menu
126075              * @param {Ext.menu.Menu} menu The menu
126076              * @param {Ext.Component} item The menu item that the mouse is over. `undefined` if not applicable.
126077              * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}
126078              */
126079             'mouseover'
126080         );
126081
126082         Ext.menu.Manager.register(me);
126083
126084         // Menu classes
126085         if (me.plain) {
126086             cls.push(prefix + 'menu-plain');
126087         }
126088         me.cls = cls.join(' ');
126089
126090         // Menu body classes
126091         bodyCls.unshift(prefix + 'menu-body');
126092         me.bodyCls = bodyCls.join(' ');
126093
126094         // Internal vbox layout, with scrolling overflow
126095         // Placed in initComponent (rather than prototype) in order to support dynamic layout/scroller
126096         // options if we wish to allow for such configurations on the Menu.
126097         // e.g., scrolling speed, vbox align stretch, etc.
126098         me.layout = {
126099             type: 'vbox',
126100             align: 'stretchmax',
126101             autoSize: true,
126102             clearInnerCtOnLayout: true,
126103             overflowHandler: 'Scroller'
126104         };
126105
126106         // hidden defaults to false if floating is configured as false
126107         if (me.floating === false && me.initialConfig.hidden !== true) {
126108             me.hidden = false;
126109         }
126110
126111         me.callParent(arguments);
126112
126113         me.on('beforeshow', function() {
126114             var hasItems = !!me.items.length;
126115             // FIXME: When a menu has its show cancelled because of no items, it
126116             // gets a visibility: hidden applied to it (instead of the default display: none)
126117             // Not sure why, but we remove this style when we want to show again.
126118             if (hasItems && me.rendered) {
126119                 me.el.setStyle('visibility', null);
126120             }
126121             return hasItems;
126122         });
126123     },
126124
126125     afterRender: function(ct) {
126126         var me = this,
126127             prefix = Ext.baseCSSPrefix,
126128             space = '&#160;';
126129
126130         me.callParent(arguments);
126131
126132         // TODO: Move this to a subTemplate When we support them in the future
126133         if (me.showSeparator) {
126134             me.iconSepEl = me.layout.getRenderTarget().insertFirst({
126135                 cls: prefix + 'menu-icon-separator',
126136                 html: space
126137             });
126138         }
126139
126140         me.focusEl = me.el.createChild({
126141             cls: prefix + 'menu-focus',
126142             tabIndex: '-1',
126143             html: space
126144         });
126145
126146         me.mon(me.el, {
126147             click: me.onClick,
126148             mouseover: me.onMouseOver,
126149             scope: me
126150         });
126151         me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);
126152
126153         if (me.showSeparator && ((!Ext.isStrict && Ext.isIE) || Ext.isIE6)) {
126154             me.iconSepEl.setHeight(me.el.getHeight());
126155         }
126156
126157         me.keyNav = Ext.create('Ext.menu.KeyNav', me);
126158     },
126159
126160     afterLayout: function() {
126161         var me = this;
126162         me.callParent(arguments);
126163
126164         // For IE6 & IE quirks, we have to resize the el and body since position: absolute
126165         // floating elements inherit their parent's width, making them the width of
126166         // document.body instead of the width of their contents.
126167         // This includes left/right dock items.
126168         if ((!Ext.isStrict && Ext.isIE) || Ext.isIE6) {
126169             var innerCt = me.layout.getRenderTarget(),
126170                 innerCtWidth = 0,
126171                 dis = me.dockedItems,
126172                 l = dis.length,
126173                 i = 0,
126174                 di, clone, newWidth;
126175
126176             innerCtWidth = innerCt.getWidth();
126177
126178             newWidth = innerCtWidth + me.body.getBorderWidth('lr') + me.body.getPadding('lr');
126179
126180             // First set the body to the new width
126181             me.body.setWidth(newWidth);
126182
126183             // Now we calculate additional width (docked items) and set the el's width
126184             for (; i < l, di = dis.getAt(i); i++) {
126185                 if (di.dock == 'left' || di.dock == 'right') {
126186                     newWidth += di.getWidth();
126187                 }
126188             }
126189             me.el.setWidth(newWidth);
126190         }
126191     },
126192     
126193     getBubbleTarget: function(){
126194         return this.parentMenu || this.callParent();
126195     },
126196
126197     /**
126198      * Returns whether a menu item can be activated or not.
126199      * @return {Boolean}
126200      */
126201     canActivateItem: function(item) {
126202         return item && !item.isDisabled() && item.isVisible() && (item.canActivate || item.getXTypes().indexOf('menuitem') < 0);
126203     },
126204
126205     /**
126206      * Deactivates the current active item on the menu, if one exists.
126207      */
126208     deactivateActiveItem: function() {
126209         var me = this;
126210
126211         if (me.activeItem) {
126212             me.activeItem.deactivate();
126213             if (!me.activeItem.activated) {
126214                 delete me.activeItem;
126215             }
126216         }
126217
126218         // only blur if focusedItem is not a filter
126219         if (me.focusedItem && !me.filtered) {
126220             me.focusedItem.blur();
126221             if (!me.focusedItem.$focused) {
126222                 delete me.focusedItem;
126223             }
126224         }
126225     },
126226
126227     clearStretch: function () {
126228         // the vbox/stretchmax will set the el sizes and subsequent layouts will not
126229         // reconsider them unless we clear the dimensions on the el's here:
126230         if (this.rendered) {
126231             this.items.each(function (item) {
126232                 // each menuItem component needs to layout again, so clear its cache
126233                 if (item.componentLayout) {
126234                     delete item.componentLayout.lastComponentSize;
126235                 }
126236                 if (item.el) {
126237                     item.el.setWidth(null);
126238                 }
126239             });
126240         }
126241     },
126242
126243     onAdd: function () {
126244         var me = this;
126245
126246         me.clearStretch();
126247         me.callParent(arguments);
126248
126249         if (Ext.isIE6 || Ext.isIE7) {
126250             // TODO - why does this need to be done (and not ok to do now)?
126251             Ext.Function.defer(me.doComponentLayout, 10, me);
126252         }
126253     },
126254
126255     onRemove: function () {
126256         this.clearStretch();
126257         this.callParent(arguments);
126258
126259     },
126260
126261     redoComponentLayout: function () {
126262         if (this.rendered) {
126263             this.clearStretch();
126264             this.doComponentLayout();
126265         }
126266     },
126267
126268     // inherit docs
126269     getFocusEl: function() {
126270         return this.focusEl;
126271     },
126272
126273     // inherit docs
126274     hide: function() {
126275         this.deactivateActiveItem();
126276         this.callParent(arguments);
126277     },
126278
126279     // private
126280     getItemFromEvent: function(e) {
126281         return this.getChildByElement(e.getTarget());
126282     },
126283
126284     lookupComponent: function(cmp) {
126285         var me = this;
126286
126287         if (Ext.isString(cmp)) {
126288             cmp = me.lookupItemFromString(cmp);
126289         } else if (Ext.isObject(cmp)) {
126290             cmp = me.lookupItemFromObject(cmp);
126291         }
126292
126293         // Apply our minWidth to all of our child components so it's accounted
126294         // for in our VBox layout
126295         cmp.minWidth = cmp.minWidth || me.minWidth;
126296
126297         return cmp;
126298     },
126299
126300     // private
126301     lookupItemFromObject: function(cmp) {
126302         var me = this,
126303             prefix = Ext.baseCSSPrefix,
126304             cls,
126305             intercept;
126306
126307         if (!cmp.isComponent) {
126308             if (!cmp.xtype) {
126309                 cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);
126310             } else {
126311                 cmp = Ext.ComponentManager.create(cmp, cmp.xtype);
126312             }
126313         }
126314
126315         if (cmp.isMenuItem) {
126316             cmp.parentMenu = me;
126317         }
126318
126319         if (!cmp.isMenuItem && !cmp.dock) {
126320             cls = [prefix + 'menu-item', prefix + 'menu-item-cmp'];
126321             intercept = Ext.Function.createInterceptor;
126322
126323             // Wrap focus/blur to control component focus
126324             cmp.focus = intercept(cmp.focus, function() {
126325                 this.$focused = true;
126326             }, cmp);
126327             cmp.blur = intercept(cmp.blur, function() {
126328                 this.$focused = false;
126329             }, cmp);
126330
126331             if (!me.plain && (cmp.indent === true || cmp.iconCls === 'no-icon')) {
126332                 cls.push(prefix + 'menu-item-indent');
126333             }
126334
126335             if (cmp.rendered) {
126336                 cmp.el.addCls(cls);
126337             } else {
126338                 cmp.cls = (cmp.cls ? cmp.cls : '') + ' ' + cls.join(' ');
126339             }
126340             cmp.isMenuItem = true;
126341         }
126342         return cmp;
126343     },
126344
126345     // private
126346     lookupItemFromString: function(cmp) {
126347         return (cmp == 'separator' || cmp == '-') ?
126348             Ext.createWidget('menuseparator')
126349             : Ext.createWidget('menuitem', {
126350                 canActivate: false,
126351                 hideOnClick: false,
126352                 plain: true,
126353                 text: cmp
126354             });
126355     },
126356
126357     onClick: function(e) {
126358         var me = this,
126359             item;
126360
126361         if (me.disabled) {
126362             e.stopEvent();
126363             return;
126364         }
126365
126366         if ((e.getTarget() == me.focusEl.dom) || e.within(me.layout.getRenderTarget())) {
126367             item = me.getItemFromEvent(e) || me.activeItem;
126368
126369             if (item) {
126370                 if (item.getXTypes().indexOf('menuitem') >= 0) {
126371                     if (!item.menu || !me.ignoreParentClicks) {
126372                         item.onClick(e);
126373                     } else {
126374                         e.stopEvent();
126375                     }
126376                 }
126377             }
126378             me.fireEvent('click', me, item, e);
126379         }
126380     },
126381
126382     onDestroy: function() {
126383         var me = this;
126384
126385         Ext.menu.Manager.unregister(me);
126386         if (me.rendered) {
126387             me.el.un(me.mouseMonitor);
126388             me.keyNav.destroy();
126389             delete me.keyNav;
126390         }
126391         me.callParent(arguments);
126392     },
126393
126394     onMouseLeave: function(e) {
126395         var me = this;
126396
126397         me.deactivateActiveItem();
126398
126399         if (me.disabled) {
126400             return;
126401         }
126402
126403         me.fireEvent('mouseleave', me, e);
126404     },
126405
126406     onMouseOver: function(e) {
126407         var me = this,
126408             fromEl = e.getRelatedTarget(),
126409             mouseEnter = !me.el.contains(fromEl),
126410             item = me.getItemFromEvent(e);
126411
126412         if (mouseEnter && me.parentMenu) {
126413             me.parentMenu.setActiveItem(me.parentItem);
126414             me.parentMenu.mouseMonitor.mouseenter();
126415         }
126416
126417         if (me.disabled) {
126418             return;
126419         }
126420
126421         if (item) {
126422             me.setActiveItem(item);
126423             if (item.activated && item.expandMenu) {
126424                 item.expandMenu();
126425             }
126426         }
126427         if (mouseEnter) {
126428             me.fireEvent('mouseenter', me, e);
126429         }
126430         me.fireEvent('mouseover', me, item, e);
126431     },
126432
126433     setActiveItem: function(item) {
126434         var me = this;
126435
126436         if (item && (item != me.activeItem && item != me.focusedItem)) {
126437             me.deactivateActiveItem();
126438             if (me.canActivateItem(item)) {
126439                 if (item.activate) {
126440                     item.activate();
126441                     if (item.activated) {
126442                         me.activeItem = item;
126443                         me.focusedItem = item;
126444                         me.focus();
126445                     }
126446                 } else {
126447                     item.focus();
126448                     me.focusedItem = item;
126449                 }
126450             }
126451             item.el.scrollIntoView(me.layout.getRenderTarget());
126452         }
126453     },
126454
126455     /**
126456      * Shows the floating menu by the specified {@link Ext.Component Component} or {@link Ext.Element Element}.
126457      * @param {Ext.Component/Ext.Element} component The {@link Ext.Component} or {@link Ext.Element} to show the menu by.
126458      * @param {String} position (optional) Alignment position as used by {@link Ext.Element#getAlignToXY}.
126459      * Defaults to `{@link #defaultAlign}`.
126460      * @param {Number[]} offsets (optional) Alignment offsets as used by {@link Ext.Element#getAlignToXY}. Defaults to `undefined`.
126461      * @return {Ext.menu.Menu} This Menu.
126462      */
126463     showBy: function(cmp, pos, off) {
126464         var me = this,
126465             xy,
126466             region;
126467
126468         if (me.floating && cmp) {
126469             me.layout.autoSize = true;
126470
126471             // show off-screen first so that we can calc position without causing a visual jump
126472             me.doAutoRender();
126473             delete me.needsLayout;
126474
126475             // Component or Element
126476             cmp = cmp.el || cmp;
126477
126478             // Convert absolute to floatParent-relative coordinates if necessary.
126479             xy = me.el.getAlignToXY(cmp, pos || me.defaultAlign, off);
126480             if (me.floatParent) {
126481                 region = me.floatParent.getTargetEl().getViewRegion();
126482                 xy[0] -= region.x;
126483                 xy[1] -= region.y;
126484             }
126485             me.showAt(xy);
126486         }
126487         return me;
126488     },
126489
126490     doConstrain : function() {
126491         var me = this,
126492             y = me.el.getY(),
126493             max, full,
126494             vector,
126495             returnY = y, normalY, parentEl, scrollTop, viewHeight;
126496
126497         delete me.height;
126498         me.setSize();
126499         full = me.getHeight();
126500         if (me.floating) {
126501             //if our reset css is scoped, there will be a x-reset wrapper on this menu which we need to skip
126502             parentEl = Ext.fly(me.el.getScopeParent());
126503             scrollTop = parentEl.getScroll().top;
126504             viewHeight = parentEl.getViewSize().height;
126505             //Normalize y by the scroll position for the parent element.  Need to move it into the coordinate space
126506             //of the view.
126507             normalY = y - scrollTop;
126508             max = me.maxHeight ? me.maxHeight : viewHeight - normalY;
126509             if (full > viewHeight) {
126510                 max = viewHeight;
126511                 //Set returnY equal to (0,0) in view space by reducing y by the value of normalY
126512                 returnY = y - normalY;
126513             } else if (max < full) {
126514                 returnY = y - (full - max);
126515                 max = full;
126516             }
126517         }else{
126518             max = me.getHeight();
126519         }
126520         // Always respect maxHeight
126521         if (me.maxHeight){
126522             max = Math.min(me.maxHeight, max);
126523         }
126524         if (full > max && max > 0){
126525             me.layout.autoSize = false;
126526             me.setHeight(max);
126527             if (me.showSeparator){
126528                 me.iconSepEl.setHeight(me.layout.getRenderTarget().dom.scrollHeight);
126529             }
126530         }
126531         vector = me.getConstrainVector(me.el.getScopeParent());
126532         if (vector) {
126533             me.setPosition(me.getPosition()[0] + vector[0]);
126534         }
126535         me.el.setY(returnY);
126536     }
126537 });
126538
126539 /**
126540  * A menu containing a Ext.picker.Color Component.
126541  *
126542  * Notes:
126543  *
126544  *   - Although not listed here, the **constructor** for this class accepts all of the
126545  *     configuration options of {@link Ext.picker.Color}.
126546  *   - If subclassing ColorMenu, any configuration options for the ColorPicker must be
126547  *     applied to the **initialConfig** property of the ColorMenu. Applying
126548  *     {@link Ext.picker.Color ColorPicker} configuration settings to `this` will **not**
126549  *     affect the ColorPicker's configuration.
126550  *
126551  * Example:
126552  *
126553  *     @example
126554  *     var colorPicker = Ext.create('Ext.menu.ColorPicker', {
126555  *         value: '000000'
126556  *     });
126557  *
126558  *     Ext.create('Ext.menu.Menu', {
126559  *         width: 100,
126560  *         height: 90,
126561  *         floating: false,  // usually you want this set to True (default)
126562  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
126563  *         items: [{
126564  *             text: 'choose a color',
126565  *             menu: colorPicker
126566  *         },{
126567  *             iconCls: 'add16',
126568  *             text: 'icon item'
126569  *         },{
126570  *             text: 'regular item'
126571  *         }]
126572  *     });
126573  */
126574  Ext.define('Ext.menu.ColorPicker', {
126575      extend: 'Ext.menu.Menu',
126576
126577      alias: 'widget.colormenu',
126578
126579      requires: [
126580         'Ext.picker.Color'
126581      ],
126582
126583     /**
126584      * @cfg {Boolean} hideOnClick
126585      * False to continue showing the menu after a date is selected.
126586      */
126587     hideOnClick : true,
126588
126589     /**
126590      * @cfg {String} pickerId
126591      * An id to assign to the underlying color picker.
126592      */
126593     pickerId : null,
126594
126595     /**
126596      * @cfg {Number} maxHeight
126597      * @hide
126598      */
126599
126600     /**
126601      * @property {Ext.picker.Color} picker
126602      * The {@link Ext.picker.Color} instance for this ColorMenu
126603      */
126604
126605     /**
126606      * @event click
126607      * @hide
126608      */
126609
126610     /**
126611      * @event itemclick
126612      * @hide
126613      */
126614
126615     initComponent : function(){
126616         var me = this,
126617             cfg = Ext.apply({}, me.initialConfig);
126618
126619         // Ensure we don't get duplicate listeners
126620         delete cfg.listeners;
126621         Ext.apply(me, {
126622             plain: true,
126623             showSeparator: false,
126624             items: Ext.applyIf({
126625                 cls: Ext.baseCSSPrefix + 'menu-color-item',
126626                 id: me.pickerId,
126627                 xtype: 'colorpicker'
126628             }, cfg)
126629         });
126630
126631         me.callParent(arguments);
126632
126633         me.picker = me.down('colorpicker');
126634
126635         /**
126636          * @event select
126637          * @alias Ext.picker.Color#select
126638          */
126639         me.relayEvents(me.picker, ['select']);
126640
126641         if (me.hideOnClick) {
126642             me.on('select', me.hidePickerOnSelect, me);
126643         }
126644     },
126645
126646     /**
126647      * Hides picker on select if hideOnClick is true
126648      * @private
126649      */
126650     hidePickerOnSelect: function() {
126651         Ext.menu.Manager.hideAll();
126652     }
126653  });
126654 /**
126655  * A menu containing an Ext.picker.Date Component.
126656  *
126657  * Notes:
126658  *
126659  * - Although not listed here, the **constructor** for this class accepts all of the
126660  *   configuration options of **{@link Ext.picker.Date}**.
126661  * - If subclassing DateMenu, any configuration options for the DatePicker must be applied
126662  *   to the **initialConfig** property of the DateMenu. Applying {@link Ext.picker.Date Date Picker}
126663  *   configuration settings to **this** will **not** affect the Date Picker's configuration.
126664  *
126665  * Example:
126666  *
126667  *     @example
126668  *     var dateMenu = Ext.create('Ext.menu.DatePicker', {
126669  *         handler: function(dp, date){
126670  *             Ext.Msg.alert('Date Selected', 'You selected ' + Ext.Date.format(date, 'M j, Y'));
126671  *         }
126672  *     });
126673  *
126674  *     Ext.create('Ext.menu.Menu', {
126675  *         width: 100,
126676  *         height: 90,
126677  *         floating: false,  // usually you want this set to True (default)
126678  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
126679  *         items: [{
126680  *             text: 'choose a date',
126681  *             menu: dateMenu
126682  *         },{
126683  *             iconCls: 'add16',
126684  *             text: 'icon item'
126685  *         },{
126686  *             text: 'regular item'
126687  *         }]
126688  *     });
126689  */
126690  Ext.define('Ext.menu.DatePicker', {
126691      extend: 'Ext.menu.Menu',
126692
126693      alias: 'widget.datemenu',
126694
126695      requires: [
126696         'Ext.picker.Date'
126697      ],
126698
126699     /**
126700      * @cfg {Boolean} hideOnClick
126701      * False to continue showing the menu after a date is selected.
126702      */
126703     hideOnClick : true,
126704
126705     /**
126706      * @cfg {String} pickerId
126707      * An id to assign to the underlying date picker.
126708      */
126709     pickerId : null,
126710
126711     /**
126712      * @cfg {Number} maxHeight
126713      * @hide
126714      */
126715
126716     /**
126717      * @property {Ext.picker.Date} picker
126718      * The {@link Ext.picker.Date} instance for this DateMenu
126719      */
126720
126721     /**
126722      * @event click
126723      * @hide
126724      */
126725
126726     /**
126727      * @event itemclick
126728      * @hide
126729      */
126730
126731     initComponent : function(){
126732         var me = this;
126733
126734         Ext.apply(me, {
126735             showSeparator: false,
126736             plain: true,
126737             border: false,
126738             bodyPadding: 0, // remove the body padding from the datepicker menu item so it looks like 3.3
126739             items: Ext.applyIf({
126740                 cls: Ext.baseCSSPrefix + 'menu-date-item',
126741                 id: me.pickerId,
126742                 xtype: 'datepicker'
126743             }, me.initialConfig)
126744         });
126745
126746         me.callParent(arguments);
126747
126748         me.picker = me.down('datepicker');
126749         /**
126750          * @event select
126751          * @alias Ext.picker.Date#select
126752          */
126753         me.relayEvents(me.picker, ['select']);
126754
126755         if (me.hideOnClick) {
126756             me.on('select', me.hidePickerOnSelect, me);
126757         }
126758     },
126759
126760     hidePickerOnSelect: function() {
126761         Ext.menu.Manager.hideAll();
126762     }
126763  });
126764 /**
126765  * This class is used to display small visual icons in the header of a panel. There are a set of
126766  * 25 icons that can be specified by using the {@link #type} config. The {@link #handler} config
126767  * can be used to provide a function that will respond to any click events. In general, this class
126768  * will not be instantiated directly, rather it will be created by specifying the {@link Ext.panel.Panel#tools}
126769  * configuration on the Panel itself.
126770  *
126771  *     @example
126772  *     Ext.create('Ext.panel.Panel', {
126773  *         width: 200,
126774  *         height: 200,
126775  *         renderTo: document.body,
126776  *         title: 'A Panel',
126777  *         tools: [{
126778  *             type: 'help',
126779  *             handler: function(){
126780  *                 // show help here
126781  *             }
126782  *         }, {
126783  *             itemId: 'refresh',
126784  *             type: 'refresh',
126785  *             hidden: true,
126786  *             handler: function(){
126787  *                 // do refresh
126788  *             }
126789  *         }, {
126790  *             type: 'search',
126791  *             handler: function(event, target, owner, tool){
126792  *                 // do search
126793  *                 owner.child('#refresh').show();
126794  *             }
126795  *         }]
126796  *     });
126797  */
126798 Ext.define('Ext.panel.Tool', {
126799     extend: 'Ext.Component',
126800     requires: ['Ext.tip.QuickTipManager'],
126801     alias: 'widget.tool',
126802
126803     baseCls: Ext.baseCSSPrefix + 'tool',
126804     disabledCls: Ext.baseCSSPrefix + 'tool-disabled',
126805     toolPressedCls: Ext.baseCSSPrefix + 'tool-pressed',
126806     toolOverCls: Ext.baseCSSPrefix + 'tool-over',
126807     ariaRole: 'button',
126808     renderTpl: ['<img id="{id}-toolEl" src="{blank}" class="{baseCls}-{type}" role="presentation"/>'],
126809
126810     /**
126811      * @cfg {Function} handler
126812      * A function to execute when the tool is clicked. Arguments passed are:
126813      *
126814      * - **event** : Ext.EventObject - The click event.
126815      * - **toolEl** : Ext.Element - The tool Element.
126816      * - **owner** : Ext.panel.Header - The host panel header.
126817      * - **tool** : Ext.panel.Tool - The tool object
126818      */
126819
126820     /**
126821      * @cfg {Object} scope
126822      * The scope to execute the {@link #handler} function. Defaults to the tool.
126823      */
126824
126825     /**
126826      * @cfg {String} type
126827      * The type of tool to render. The following types are available:
126828      *
126829      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-close"></span> close
126830      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-minimize"></span> minimize
126831      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-maximize"></span> maximize
126832      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-restore"></span> restore
126833      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-toggle"></span> toggle
126834      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-gear"></span> gear
126835      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-prev"></span> prev
126836      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-next"></span> next
126837      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-pin"></span> pin
126838      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-unpin"></span> unpin
126839      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-right"></span> right
126840      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-left"></span> left
126841      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-down"></span> down
126842      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-up"></span> up
126843      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-refresh"></span> refresh
126844      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-plus"></span> plus
126845      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-minus"></span> minus
126846      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-search"></span> search
126847      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-save"></span> save
126848      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-help"></span> help
126849      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-print"></span> print
126850      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-expand"></span> expand
126851      * - <span class="x-tool"><img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" class="x-tool-collapse"></span> collapse
126852      */
126853
126854     /**
126855      * @cfg {String/Object} tooltip
126856      * The tooltip for the tool - can be a string to be used as innerHTML (html tags are accepted) or QuickTips config
126857      * object
126858      */
126859
126860      /**
126861      * @cfg {String} tooltipType
126862      * The type of tooltip to use. Either 'qtip' (default) for QuickTips or 'title' for title attribute.
126863      */
126864     tooltipType: 'qtip',
126865
126866     /**
126867      * @cfg {Boolean} stopEvent
126868      * Specify as false to allow click event to propagate.
126869      */
126870     stopEvent: true,
126871
126872     initComponent: function() {
126873         var me = this;
126874         me.addEvents(
126875             /**
126876              * @event click
126877              * Fires when the tool is clicked
126878              * @param {Ext.panel.Tool} this
126879              * @param {Ext.EventObject} e The event object
126880              */
126881             'click'
126882         );
126883
126884
126885         me.type = me.type || me.id;
126886
126887         Ext.applyIf(me.renderData, {
126888             baseCls: me.baseCls,
126889             blank: Ext.BLANK_IMAGE_URL,
126890             type: me.type
126891         });
126892
126893         me.addChildEls('toolEl');
126894
126895         // alias qtip, should use tooltip since it's what we have in the docs
126896         me.tooltip = me.tooltip || me.qtip;
126897         me.callParent();
126898     },
126899
126900     // inherit docs
126901     afterRender: function() {
126902         var me = this,
126903             attr;
126904
126905         me.callParent(arguments);
126906         if (me.tooltip) {
126907             if (Ext.isObject(me.tooltip)) {
126908                 Ext.tip.QuickTipManager.register(Ext.apply({
126909                     target: me.id
126910                 }, me.tooltip));
126911             }
126912             else {
126913                 attr = me.tooltipType == 'qtip' ? 'data-qtip' : 'title';
126914                 me.toolEl.dom.setAttribute(attr, me.tooltip);
126915             }
126916         }
126917
126918         me.mon(me.toolEl, {
126919             click: me.onClick,
126920             mousedown: me.onMouseDown,
126921             mouseover: me.onMouseOver,
126922             mouseout: me.onMouseOut,
126923             scope: me
126924         });
126925     },
126926
126927     /**
126928      * Sets the type of the tool. Allows the icon to be changed.
126929      * @param {String} type The new type. See the {@link #type} config.
126930      * @return {Ext.panel.Tool} this
126931      */
126932     setType: function(type) {
126933         var me = this;
126934
126935         me.type = type;
126936         if (me.rendered) {
126937             me.toolEl.dom.className = me.baseCls + '-' + type;
126938         }
126939         return me;
126940     },
126941
126942     /**
126943      * Binds this tool to a component.
126944      * @private
126945      * @param {Ext.Component} component The component
126946      */
126947     bindTo: function(component) {
126948         this.owner = component;
126949     },
126950
126951     /**
126952      * Called when the tool element is clicked
126953      * @private
126954      * @param {Ext.EventObject} e
126955      * @param {HTMLElement} target The target element
126956      */
126957     onClick: function(e, target) {
126958         var me = this,
126959             owner;
126960
126961         if (me.disabled) {
126962             return false;
126963         }
126964         owner = me.owner || me.ownerCt;
126965
126966         //remove the pressed + over class
126967         me.el.removeCls(me.toolPressedCls);
126968         me.el.removeCls(me.toolOverCls);
126969
126970         if (me.stopEvent !== false) {
126971             e.stopEvent();
126972         }
126973
126974         Ext.callback(me.handler, me.scope || me, [e, target, owner, me]);
126975         me.fireEvent('click', me, e);
126976         return true;
126977     },
126978
126979     // inherit docs
126980     onDestroy: function(){
126981         if (Ext.isObject(this.tooltip)) {
126982             Ext.tip.QuickTipManager.unregister(this.id);
126983         }
126984         this.callParent();
126985     },
126986
126987     /**
126988      * Called when the user presses their mouse button down on a tool
126989      * Adds the press class ({@link #toolPressedCls})
126990      * @private
126991      */
126992     onMouseDown: function() {
126993         if (this.disabled) {
126994             return false;
126995         }
126996
126997         this.el.addCls(this.toolPressedCls);
126998     },
126999
127000     /**
127001      * Called when the user rolls over a tool
127002      * Adds the over class ({@link #toolOverCls})
127003      * @private
127004      */
127005     onMouseOver: function() {
127006         if (this.disabled) {
127007             return false;
127008         }
127009         this.el.addCls(this.toolOverCls);
127010     },
127011
127012     /**
127013      * Called when the user rolls out from a tool.
127014      * Removes the over class ({@link #toolOverCls})
127015      * @private
127016      */
127017     onMouseOut: function() {
127018         this.el.removeCls(this.toolOverCls);
127019     }
127020 });
127021 /**
127022  * @class Ext.resizer.Handle
127023  * @extends Ext.Component
127024  *
127025  * Provides a handle for 9-point resizing of Elements or Components.
127026  */
127027 Ext.define('Ext.resizer.Handle', {
127028     extend: 'Ext.Component',
127029     handleCls: '',
127030     baseHandleCls: Ext.baseCSSPrefix + 'resizable-handle',
127031     // Ext.resizer.Resizer.prototype.possiblePositions define the regions
127032     // which will be passed in as a region configuration.
127033     region: '',
127034
127035     onRender: function() {
127036         this.addCls(
127037             this.baseHandleCls,
127038             this.baseHandleCls + '-' + this.region,
127039             this.handleCls
127040         );
127041         this.callParent(arguments);
127042         this.el.unselectable();
127043     }
127044 });
127045
127046 /**
127047  * Applies drag handles to an element or component to make it resizable. The drag handles are inserted into the element
127048  * (or component's element) and positioned absolute.
127049  *
127050  * Textarea and img elements will be wrapped with an additional div because these elements do not support child nodes.
127051  * The original element can be accessed through the originalTarget property.
127052  *
127053  * Here is the list of valid resize handles:
127054  *
127055  *     Value   Description
127056  *     ------  -------------------
127057  *      'n'     north
127058  *      's'     south
127059  *      'e'     east
127060  *      'w'     west
127061  *      'nw'    northwest
127062  *      'sw'    southwest
127063  *      'se'    southeast
127064  *      'ne'    northeast
127065  *      'all'   all
127066  *
127067  * {@img Ext.resizer.Resizer/Ext.resizer.Resizer.png Ext.resizer.Resizer component}
127068  *
127069  * Here's an example showing the creation of a typical Resizer:
127070  *
127071  *     Ext.create('Ext.resizer.Resizer', {
127072  *         el: 'elToResize',
127073  *         handles: 'all',
127074  *         minWidth: 200,
127075  *         minHeight: 100,
127076  *         maxWidth: 500,
127077  *         maxHeight: 400,
127078  *         pinned: true
127079  *     });
127080  */
127081 Ext.define('Ext.resizer.Resizer', {
127082     mixins: {
127083         observable: 'Ext.util.Observable'
127084     },
127085     uses: ['Ext.resizer.ResizeTracker', 'Ext.Component'],
127086
127087     alternateClassName: 'Ext.Resizable',
127088
127089     handleCls: Ext.baseCSSPrefix + 'resizable-handle',
127090     pinnedCls: Ext.baseCSSPrefix + 'resizable-pinned',
127091     overCls:   Ext.baseCSSPrefix + 'resizable-over',
127092     wrapCls:   Ext.baseCSSPrefix + 'resizable-wrap',
127093
127094     /**
127095      * @cfg {Boolean} dynamic
127096      * Specify as true to update the {@link #target} (Element or {@link Ext.Component Component}) dynamically during
127097      * dragging. This is `true` by default, but the {@link Ext.Component Component} class passes `false` when it is
127098      * configured as {@link Ext.Component#resizable}.
127099      *
127100      * If specified as `false`, a proxy element is displayed during the resize operation, and the {@link #target} is
127101      * updated on mouseup.
127102      */
127103     dynamic: true,
127104
127105     /**
127106      * @cfg {String} handles
127107      * String consisting of the resize handles to display. Defaults to 's e se' for Elements and fixed position
127108      * Components. Defaults to 8 point resizing for floating Components (such as Windows). Specify either `'all'` or any
127109      * of `'n s e w ne nw se sw'`.
127110      */
127111     handles: 's e se',
127112
127113     /**
127114      * @cfg {Number} height
127115      * Optional. The height to set target to in pixels
127116      */
127117     height : null,
127118
127119     /**
127120      * @cfg {Number} width
127121      * Optional. The width to set the target to in pixels
127122      */
127123     width : null,
127124
127125     /**
127126      * @cfg {Number} heightIncrement
127127      * The increment to snap the height resize in pixels.
127128      */
127129     heightIncrement : 0,
127130
127131     /**
127132      * @cfg {Number} widthIncrement
127133      * The increment to snap the width resize in pixels.
127134      */
127135     widthIncrement : 0,
127136
127137     /**
127138      * @cfg {Number} minHeight
127139      * The minimum height for the element
127140      */
127141     minHeight : 20,
127142
127143     /**
127144      * @cfg {Number} minWidth
127145      * The minimum width for the element
127146      */
127147     minWidth : 20,
127148
127149     /**
127150      * @cfg {Number} maxHeight
127151      * The maximum height for the element
127152      */
127153     maxHeight : 10000,
127154
127155     /**
127156      * @cfg {Number} maxWidth
127157      * The maximum width for the element
127158      */
127159     maxWidth : 10000,
127160
127161     /**
127162      * @cfg {Boolean} pinned
127163      * True to ensure that the resize handles are always visible, false indicates resizing by cursor changes only
127164      */
127165     pinned: false,
127166
127167     /**
127168      * @cfg {Boolean} preserveRatio
127169      * True to preserve the original ratio between height and width during resize
127170      */
127171     preserveRatio: false,
127172
127173     /**
127174      * @cfg {Boolean} transparent
127175      * True for transparent handles. This is only applied at config time.
127176      */
127177     transparent: false,
127178
127179     /**
127180      * @cfg {Ext.Element/Ext.util.Region} constrainTo
127181      * An element, or a {@link Ext.util.Region Region} into which the resize operation must be constrained.
127182      */
127183
127184     possiblePositions: {
127185         n:  'north',
127186         s:  'south',
127187         e:  'east',
127188         w:  'west',
127189         se: 'southeast',
127190         sw: 'southwest',
127191         nw: 'northwest',
127192         ne: 'northeast'
127193     },
127194
127195     /**
127196      * @cfg {Ext.Element/Ext.Component} target
127197      * The Element or Component to resize.
127198      */
127199
127200     /**
127201      * @property {Ext.Element} el
127202      * Outer element for resizing behavior.
127203      */
127204
127205     constructor: function(config) {
127206         var me = this,
127207             target,
127208             tag,
127209             handles = me.handles,
127210             handleCls,
127211             possibles,
127212             len,
127213             i = 0,
127214             pos;
127215
127216         this.addEvents(
127217             /**
127218              * @event beforeresize
127219              * Fired before resize is allowed. Return false to cancel resize.
127220              * @param {Ext.resizer.Resizer} this
127221              * @param {Number} width The start width
127222              * @param {Number} height The start height
127223              * @param {Ext.EventObject} e The mousedown event
127224              */
127225             'beforeresize',
127226             /**
127227              * @event resizedrag
127228              * Fires during resizing. Return false to cancel resize.
127229              * @param {Ext.resizer.Resizer} this
127230              * @param {Number} width The new width
127231              * @param {Number} height The new height
127232              * @param {Ext.EventObject} e The mousedown event
127233              */
127234             'resizedrag',
127235             /**
127236              * @event resize
127237              * Fired after a resize.
127238              * @param {Ext.resizer.Resizer} this
127239              * @param {Number} width The new width
127240              * @param {Number} height The new height
127241              * @param {Ext.EventObject} e The mouseup event
127242              */
127243             'resize'
127244         );
127245
127246         if (Ext.isString(config) || Ext.isElement(config) || config.dom) {
127247             target = config;
127248             config = arguments[1] || {};
127249             config.target = target;
127250         }
127251         // will apply config to this
127252         me.mixins.observable.constructor.call(me, config);
127253
127254         // If target is a Component, ensure that we pull the element out.
127255         // Resizer must examine the underlying Element.
127256         target = me.target;
127257         if (target) {
127258             if (target.isComponent) {
127259                 me.el = target.getEl();
127260                 if (target.minWidth) {
127261                     me.minWidth = target.minWidth;
127262                 }
127263                 if (target.minHeight) {
127264                     me.minHeight = target.minHeight;
127265                 }
127266                 if (target.maxWidth) {
127267                     me.maxWidth = target.maxWidth;
127268                 }
127269                 if (target.maxHeight) {
127270                     me.maxHeight = target.maxHeight;
127271                 }
127272                 if (target.floating) {
127273                     if (!this.hasOwnProperty('handles')) {
127274                         this.handles = 'n ne e se s sw w nw';
127275                     }
127276                 }
127277             } else {
127278                 me.el = me.target = Ext.get(target);
127279             }
127280         }
127281         // Backwards compatibility with Ext3.x's Resizable which used el as a config.
127282         else {
127283             me.target = me.el = Ext.get(me.el);
127284         }
127285
127286         // Tags like textarea and img cannot
127287         // have children and therefore must
127288         // be wrapped
127289         tag = me.el.dom.tagName;
127290         if (tag == 'TEXTAREA' || tag == 'IMG') {
127291             /**
127292              * @property {Ext.Element/Ext.Component} originalTarget
127293              * Reference to the original resize target if the element of the original resize target was an IMG or a
127294              * TEXTAREA which must be wrapped in a DIV.
127295              */
127296             me.originalTarget = me.target;
127297             me.target = me.el = me.el.wrap({
127298                 cls: me.wrapCls,
127299                 id: me.el.id + '-rzwrap'
127300             });
127301
127302             // Transfer originalTarget's positioning/sizing
127303             me.el.setPositioning(me.originalTarget.getPositioning());
127304             me.originalTarget.clearPositioning();
127305             var box = me.originalTarget.getBox();
127306             me.el.setBox(box);
127307         }
127308
127309         // Position the element, this enables us to absolute position
127310         // the handles within this.el
127311         me.el.position();
127312         if (me.pinned) {
127313             me.el.addCls(me.pinnedCls);
127314         }
127315
127316         /**
127317          * @property {Ext.resizer.ResizeTracker} resizeTracker
127318          */
127319         me.resizeTracker = Ext.create('Ext.resizer.ResizeTracker', {
127320             disabled: me.disabled,
127321             target: me.target,
127322             constrainTo: me.constrainTo,
127323             overCls: me.overCls,
127324             throttle: me.throttle,
127325             originalTarget: me.originalTarget,
127326             delegate: '.' + me.handleCls,
127327             dynamic: me.dynamic,
127328             preserveRatio: me.preserveRatio,
127329             heightIncrement: me.heightIncrement,
127330             widthIncrement: me.widthIncrement,
127331             minHeight: me.minHeight,
127332             maxHeight: me.maxHeight,
127333             minWidth: me.minWidth,
127334             maxWidth: me.maxWidth
127335         });
127336
127337         // Relay the ResizeTracker's superclass events as our own resize events
127338         me.resizeTracker.on('mousedown', me.onBeforeResize, me);
127339         me.resizeTracker.on('drag', me.onResize, me);
127340         me.resizeTracker.on('dragend', me.onResizeEnd, me);
127341
127342         if (me.handles == 'all') {
127343             me.handles = 'n s e w ne nw se sw';
127344         }
127345
127346         handles = me.handles = me.handles.split(/ |\s*?[,;]\s*?/);
127347         possibles = me.possiblePositions;
127348         len = handles.length;
127349         handleCls = me.handleCls + ' ' + (this.target.isComponent ? (me.target.baseCls + '-handle ') : '') + me.handleCls + '-';
127350
127351         for(; i < len; i++){
127352             // if specified and possible, create
127353             if (handles[i] && possibles[handles[i]]) {
127354                 pos = possibles[handles[i]];
127355                 // store a reference in this.east, this.west, etc
127356
127357                 me[pos] = Ext.create('Ext.Component', {
127358                     owner: this,
127359                     region: pos,
127360                     cls: handleCls + pos,
127361                     renderTo: me.el
127362                 });
127363                 me[pos].el.unselectable();
127364                 if (me.transparent) {
127365                     me[pos].el.setOpacity(0);
127366                 }
127367             }
127368         }
127369
127370         // Constrain within configured maxima
127371         if (Ext.isNumber(me.width)) {
127372             me.width = Ext.Number.constrain(me.width, me.minWidth, me.maxWidth);
127373         }
127374         if (Ext.isNumber(me.height)) {
127375             me.height = Ext.Number.constrain(me.height, me.minHeight, me.maxHeight);
127376         }
127377
127378         // Size the element
127379         if (me.width != null || me.height != null) {
127380             if (me.originalTarget) {
127381                 me.originalTarget.setWidth(me.width);
127382                 me.originalTarget.setHeight(me.height);
127383             }
127384             me.resizeTo(me.width, me.height);
127385         }
127386
127387         me.forceHandlesHeight();
127388     },
127389
127390     disable: function() {
127391         this.resizeTracker.disable();
127392     },
127393
127394     enable: function() {
127395         this.resizeTracker.enable();
127396     },
127397
127398     /**
127399      * @private Relay the Tracker's mousedown event as beforeresize
127400      * @param tracker The Resizer
127401      * @param e The Event
127402      */
127403     onBeforeResize: function(tracker, e) {
127404         var b = this.target.getBox();
127405         return this.fireEvent('beforeresize', this, b.width, b.height, e);
127406     },
127407
127408     /**
127409      * @private Relay the Tracker's drag event as resizedrag
127410      * @param tracker The Resizer
127411      * @param e The Event
127412      */
127413     onResize: function(tracker, e) {
127414         var me = this,
127415             b = me.target.getBox();
127416         me.forceHandlesHeight();
127417         return me.fireEvent('resizedrag', me, b.width, b.height, e);
127418     },
127419
127420     /**
127421      * @private Relay the Tracker's dragend event as resize
127422      * @param tracker The Resizer
127423      * @param e The Event
127424      */
127425     onResizeEnd: function(tracker, e) {
127426         var me = this,
127427             b = me.target.getBox();
127428         me.forceHandlesHeight();
127429         return me.fireEvent('resize', me, b.width, b.height, e);
127430     },
127431
127432     /**
127433      * Perform a manual resize and fires the 'resize' event.
127434      * @param {Number} width
127435      * @param {Number} height
127436      */
127437     resizeTo : function(width, height){
127438         this.target.setSize(width, height);
127439         this.fireEvent('resize', this, width, height, null);
127440     },
127441
127442     /**
127443      * Returns the element that was configured with the el or target config property. If a component was configured with
127444      * the target property then this will return the element of this component.
127445      *
127446      * Textarea and img elements will be wrapped with an additional div because these elements do not support child
127447      * nodes. The original element can be accessed through the originalTarget property.
127448      * @return {Ext.Element} element
127449      */
127450     getEl : function() {
127451         return this.el;
127452     },
127453
127454     /**
127455      * Returns the element or component that was configured with the target config property.
127456      *
127457      * Textarea and img elements will be wrapped with an additional div because these elements do not support child
127458      * nodes. The original element can be accessed through the originalTarget property.
127459      * @return {Ext.Element/Ext.Component}
127460      */
127461     getTarget: function() {
127462         return this.target;
127463     },
127464
127465     destroy: function() {
127466         var h;
127467         for (var i = 0, l = this.handles.length; i < l; i++) {
127468             h = this[this.possiblePositions[this.handles[i]]];
127469             delete h.owner;
127470             Ext.destroy(h);
127471         }
127472     },
127473
127474     /**
127475      * @private
127476      * Fix IE6 handle height issue.
127477      */
127478     forceHandlesHeight : function() {
127479         var me = this,
127480             handle;
127481         if (Ext.isIE6) {
127482             handle = me.east;
127483             if (handle) {
127484                 handle.setHeight(me.el.getHeight());
127485             }
127486             handle = me.west;
127487             if (handle) {
127488                 handle.setHeight(me.el.getHeight());
127489             }
127490             me.el.repaint();
127491         }
127492     }
127493 });
127494
127495 /**
127496  * @class Ext.resizer.ResizeTracker
127497  * @extends Ext.dd.DragTracker
127498  * Private utility class for Ext.resizer.Resizer.
127499  * @private
127500  */
127501 Ext.define('Ext.resizer.ResizeTracker', {
127502     extend: 'Ext.dd.DragTracker',
127503     dynamic: true,
127504     preserveRatio: false,
127505
127506     // Default to no constraint
127507     constrainTo: null,
127508     
127509     proxyCls:  Ext.baseCSSPrefix + 'resizable-proxy',
127510
127511     constructor: function(config) {
127512         var me = this;
127513
127514         if (!config.el) {
127515             if (config.target.isComponent) {
127516                 me.el = config.target.getEl();
127517             } else {
127518                 me.el = config.target;
127519             }
127520         }
127521         this.callParent(arguments);
127522
127523         // Ensure that if we are preserving aspect ratio, the largest minimum is honoured
127524         if (me.preserveRatio && me.minWidth && me.minHeight) {
127525             var widthRatio = me.minWidth / me.el.getWidth(),
127526                 heightRatio = me.minHeight / me.el.getHeight();
127527
127528             // largest ratio of minimum:size must be preserved.
127529             // So if a 400x200 pixel image has
127530             // minWidth: 50, maxWidth: 50, the maxWidth will be 400 * (50/200)... that is 100
127531             if (heightRatio > widthRatio) {
127532                 me.minWidth = me.el.getWidth() * heightRatio;
127533             } else {
127534                 me.minHeight = me.el.getHeight() * widthRatio;
127535             }
127536         }
127537
127538         // If configured as throttled, create an instance version of resize which calls
127539         // a throttled function to perform the resize operation.
127540         if (me.throttle) {
127541             var throttledResizeFn = Ext.Function.createThrottled(function() {
127542                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
127543                 }, me.throttle);
127544
127545             me.resize = function(box, direction, atEnd) {
127546                 if (atEnd) {
127547                     Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
127548                 } else {
127549                     throttledResizeFn.apply(null, arguments);
127550                 }
127551             };
127552         }
127553     },
127554
127555     onBeforeStart: function(e) {
127556         // record the startBox
127557         this.startBox = this.el.getBox();
127558     },
127559
127560     /**
127561      * @private
127562      * Returns the object that will be resized on every mousemove event.
127563      * If dynamic is false, this will be a proxy, otherwise it will be our actual target.
127564      */
127565     getDynamicTarget: function() {
127566         var me = this,
127567             target = me.target;
127568             
127569         if (me.dynamic) {
127570             return target;
127571         } else if (!me.proxy) {
127572             me.proxy = me.createProxy(target);
127573         }
127574         me.proxy.show();
127575         return me.proxy;
127576     },
127577     
127578     /**
127579      * Create a proxy for this resizer
127580      * @param {Ext.Component/Ext.Element} target The target
127581      * @return {Ext.Element} A proxy element
127582      */
127583     createProxy: function(target){
127584         var proxy,
127585             cls = this.proxyCls,
127586             renderTo;
127587             
127588         if (target.isComponent) {
127589             proxy = target.getProxy().addCls(cls);
127590         } else {
127591             renderTo = Ext.getBody();
127592             if (Ext.scopeResetCSS) {
127593                 renderTo = Ext.getBody().createChild({
127594                     cls: Ext.baseCSSPrefix + 'reset'
127595                 });
127596             }
127597             proxy = target.createProxy({
127598                 tag: 'div',
127599                 cls: cls,
127600                 id: target.id + '-rzproxy'
127601             }, renderTo);
127602         }
127603         proxy.removeCls(Ext.baseCSSPrefix + 'proxy-el');
127604         return proxy;
127605     },
127606
127607     onStart: function(e) {
127608         // returns the Ext.ResizeHandle that the user started dragging
127609         this.activeResizeHandle = Ext.getCmp(this.getDragTarget().id);
127610
127611         // If we are using a proxy, ensure it is sized.
127612         if (!this.dynamic) {
127613             this.resize(this.startBox, {
127614                 horizontal: 'none',
127615                 vertical: 'none'
127616             });
127617         }
127618     },
127619
127620     onDrag: function(e) {
127621         // dynamic resizing, update dimensions during resize
127622         if (this.dynamic || this.proxy) {
127623             this.updateDimensions(e);
127624         }
127625     },
127626
127627     updateDimensions: function(e, atEnd) {
127628         var me = this,
127629             region = me.activeResizeHandle.region,
127630             offset = me.getOffset(me.constrainTo ? 'dragTarget' : null),
127631             box = me.startBox,
127632             ratio,
127633             widthAdjust = 0,
127634             heightAdjust = 0,
127635             snappedWidth,
127636             snappedHeight,
127637             adjustX = 0,
127638             adjustY = 0,
127639             dragRatio,
127640             horizDir = offset[0] < 0 ? 'right' : 'left',
127641             vertDir = offset[1] < 0 ? 'down' : 'up',
127642             oppositeCorner,
127643             axis; // 1 = x, 2 = y, 3 = x and y.
127644
127645         switch (region) {
127646             case 'south':
127647                 heightAdjust = offset[1];
127648                 axis = 2;
127649                 break;
127650             case 'north':
127651                 heightAdjust = -offset[1];
127652                 adjustY = -heightAdjust;
127653                 axis = 2;
127654                 break;
127655             case 'east':
127656                 widthAdjust = offset[0];
127657                 axis = 1;
127658                 break;
127659             case 'west':
127660                 widthAdjust = -offset[0];
127661                 adjustX = -widthAdjust;
127662                 axis = 1;
127663                 break;
127664             case 'northeast':
127665                 heightAdjust = -offset[1];
127666                 adjustY = -heightAdjust;
127667                 widthAdjust = offset[0];
127668                 oppositeCorner = [box.x, box.y + box.height];
127669                 axis = 3;
127670                 break;
127671             case 'southeast':
127672                 heightAdjust = offset[1];
127673                 widthAdjust = offset[0];
127674                 oppositeCorner = [box.x, box.y];
127675                 axis = 3;
127676                 break;
127677             case 'southwest':
127678                 widthAdjust = -offset[0];
127679                 adjustX = -widthAdjust;
127680                 heightAdjust = offset[1];
127681                 oppositeCorner = [box.x + box.width, box.y];
127682                 axis = 3;
127683                 break;
127684             case 'northwest':
127685                 heightAdjust = -offset[1];
127686                 adjustY = -heightAdjust;
127687                 widthAdjust = -offset[0];
127688                 adjustX = -widthAdjust;
127689                 oppositeCorner = [box.x + box.width, box.y + box.height];
127690                 axis = 3;
127691                 break;
127692         }
127693
127694         var newBox = {
127695             width: box.width + widthAdjust,
127696             height: box.height + heightAdjust,
127697             x: box.x + adjustX,
127698             y: box.y + adjustY
127699         };
127700
127701         // Snap value between stops according to configured increments
127702         snappedWidth = Ext.Number.snap(newBox.width, me.widthIncrement);
127703         snappedHeight = Ext.Number.snap(newBox.height, me.heightIncrement);
127704         if (snappedWidth != newBox.width || snappedHeight != newBox.height){
127705             switch (region) {
127706                 case 'northeast':
127707                     newBox.y -= snappedHeight - newBox.height;
127708                     break;
127709                 case 'north':
127710                     newBox.y -= snappedHeight - newBox.height;
127711                     break;
127712                 case 'southwest':
127713                     newBox.x -= snappedWidth - newBox.width;
127714                     break;
127715                 case 'west':
127716                     newBox.x -= snappedWidth - newBox.width;
127717                     break;
127718                 case 'northwest':
127719                     newBox.x -= snappedWidth - newBox.width;
127720                     newBox.y -= snappedHeight - newBox.height;
127721             }
127722             newBox.width = snappedWidth;
127723             newBox.height = snappedHeight;
127724         }
127725
127726         // out of bounds
127727         if (newBox.width < me.minWidth || newBox.width > me.maxWidth) {
127728             newBox.width = Ext.Number.constrain(newBox.width, me.minWidth, me.maxWidth);
127729
127730             // Re-adjust the X position if we were dragging the west side
127731             if (adjustX) {
127732                 newBox.x = box.x + (box.width - newBox.width);
127733             }
127734         } else {
127735             me.lastX = newBox.x;
127736         }
127737         if (newBox.height < me.minHeight || newBox.height > me.maxHeight) {
127738             newBox.height = Ext.Number.constrain(newBox.height, me.minHeight, me.maxHeight);
127739
127740             // Re-adjust the Y position if we were dragging the north side
127741             if (adjustY) {
127742                 newBox.y = box.y + (box.height - newBox.height);
127743             }
127744         } else {
127745             me.lastY = newBox.y;
127746         }
127747
127748         // If this is configured to preserve the aspect ratio, or they are dragging using the shift key
127749         if (me.preserveRatio || e.shiftKey) {
127750             var newHeight,
127751                 newWidth;
127752
127753             ratio = me.startBox.width / me.startBox.height;
127754
127755             // Calculate aspect ratio constrained values.
127756             newHeight = Math.min(Math.max(me.minHeight, newBox.width / ratio), me.maxHeight);
127757             newWidth = Math.min(Math.max(me.minWidth, newBox.height * ratio), me.maxWidth);
127758
127759             // X axis: width-only change, height must obey
127760             if (axis == 1) {
127761                 newBox.height = newHeight;
127762             }
127763
127764             // Y axis: height-only change, width must obey
127765             else if (axis == 2) {
127766                 newBox.width = newWidth;
127767             }
127768
127769             // Corner drag.
127770             else {
127771                 // Drag ratio is the ratio of the mouse point from the opposite corner.
127772                 // Basically what edge we are dragging, a horizontal edge or a vertical edge.
127773                 dragRatio = Math.abs(oppositeCorner[0] - this.lastXY[0]) / Math.abs(oppositeCorner[1] - this.lastXY[1]);
127774
127775                 // If drag ratio > aspect ratio then width is dominant and height must obey
127776                 if (dragRatio > ratio) {
127777                     newBox.height = newHeight;
127778                 } else {
127779                     newBox.width = newWidth;
127780                 }
127781
127782                 // Handle dragging start coordinates
127783                 if (region == 'northeast') {
127784                     newBox.y = box.y - (newBox.height - box.height);
127785                 } else if (region == 'northwest') {
127786                     newBox.y = box.y - (newBox.height - box.height);
127787                     newBox.x = box.x - (newBox.width - box.width);
127788                 } else if (region == 'southwest') {
127789                     newBox.x = box.x - (newBox.width - box.width);
127790                 }
127791             }
127792         }
127793
127794         if (heightAdjust === 0) {
127795             vertDir = 'none';
127796         }
127797         if (widthAdjust === 0) {
127798             horizDir = 'none';
127799         }
127800         me.resize(newBox, {
127801             horizontal: horizDir,
127802             vertical: vertDir
127803         }, atEnd);
127804     },
127805
127806     getResizeTarget: function(atEnd) {
127807         return atEnd ? this.target : this.getDynamicTarget();
127808     },
127809
127810     resize: function(box, direction, atEnd) {
127811         var target = this.getResizeTarget(atEnd);
127812         if (target.isComponent) {
127813             if (target.floating) {
127814                 target.setPagePosition(box.x, box.y);
127815             }
127816             target.setSize(box.width, box.height);
127817         } else {
127818             target.setBox(box);
127819             // update the originalTarget if this was wrapped.
127820             if (this.originalTarget) {
127821                 this.originalTarget.setBox(box);
127822             }
127823         }
127824     },
127825
127826     onEnd: function(e) {
127827         this.updateDimensions(e, true);
127828         if (this.proxy) {
127829             this.proxy.hide();
127830         }
127831     }
127832 });
127833
127834 /**
127835  * @class Ext.resizer.SplitterTracker
127836  * @extends Ext.dd.DragTracker
127837  * Private utility class for Ext.Splitter.
127838  * @private
127839  */
127840 Ext.define('Ext.resizer.SplitterTracker', {
127841     extend: 'Ext.dd.DragTracker',
127842     requires: ['Ext.util.Region'],
127843     enabled: true,
127844     
127845     overlayCls: Ext.baseCSSPrefix + 'resizable-overlay',
127846
127847     getPrevCmp: function() {
127848         var splitter = this.getSplitter();
127849         return splitter.previousSibling();
127850     },
127851
127852     getNextCmp: function() {
127853         var splitter = this.getSplitter();
127854         return splitter.nextSibling();
127855     },
127856
127857     // ensure the tracker is enabled, store boxes of previous and next
127858     // components and calculate the constrain region
127859     onBeforeStart: function(e) {
127860         var me = this,
127861             prevCmp = me.getPrevCmp(),
127862             nextCmp = me.getNextCmp(),
127863             collapseEl = me.getSplitter().collapseEl,
127864             overlay;
127865             
127866         if (collapseEl && (e.getTarget() === me.getSplitter().collapseEl.dom)) {
127867             return false;
127868         }
127869
127870         // SplitterTracker is disabled if any of its adjacents are collapsed.
127871         if (nextCmp.collapsed || prevCmp.collapsed) {
127872             return false;
127873         }
127874         
127875         overlay = me.overlay =  Ext.getBody().createChild({
127876             cls: me.overlayCls, 
127877             html: '&#160;'
127878         });
127879         overlay.unselectable();
127880         overlay.setSize(Ext.Element.getViewWidth(true), Ext.Element.getViewHeight(true));
127881         overlay.show();
127882         
127883         // store boxes of previous and next
127884         me.prevBox  = prevCmp.getEl().getBox();
127885         me.nextBox  = nextCmp.getEl().getBox();
127886         me.constrainTo = me.calculateConstrainRegion();
127887     },
127888
127889     // We move the splitter el. Add the proxy class.
127890     onStart: function(e) {
127891         var splitter = this.getSplitter();
127892         splitter.addCls(splitter.baseCls + '-active');
127893     },
127894
127895     // calculate the constrain Region in which the splitter el may be moved.
127896     calculateConstrainRegion: function() {
127897         var me         = this,
127898             splitter   = me.getSplitter(),
127899             splitWidth = splitter.getWidth(),
127900             defaultMin = splitter.defaultSplitMin,
127901             orient     = splitter.orientation,
127902             prevBox    = me.prevBox,
127903             prevCmp    = me.getPrevCmp(),
127904             nextBox    = me.nextBox,
127905             nextCmp    = me.getNextCmp(),
127906             // prev and nextConstrainRegions are the maximumBoxes minus the
127907             // minimumBoxes. The result is always the intersection
127908             // of these two boxes.
127909             prevConstrainRegion, nextConstrainRegion;
127910
127911         // vertical splitters, so resizing left to right
127912         if (orient === 'vertical') {
127913
127914             // Region constructor accepts (top, right, bottom, left)
127915             // anchored/calculated from the left
127916             prevConstrainRegion = Ext.create('Ext.util.Region',
127917                 prevBox.y,
127918                 // Right boundary is x + maxWidth if there IS a maxWidth.
127919                 // Otherwise it is calculated based upon the minWidth of the next Component
127920                 (prevCmp.maxWidth ? prevBox.x + prevCmp.maxWidth : nextBox.right - (nextCmp.minWidth || defaultMin)) + splitWidth,
127921                 prevBox.bottom,
127922                 prevBox.x + (prevCmp.minWidth || defaultMin)
127923             );
127924             // anchored/calculated from the right
127925             nextConstrainRegion = Ext.create('Ext.util.Region',
127926                 nextBox.y,
127927                 nextBox.right - (nextCmp.minWidth || defaultMin),
127928                 nextBox.bottom,
127929                 // Left boundary is right - maxWidth if there IS a maxWidth.
127930                 // Otherwise it is calculated based upon the minWidth of the previous Component
127931                 (nextCmp.maxWidth ? nextBox.right - nextCmp.maxWidth : prevBox.x + (prevBox.minWidth || defaultMin)) - splitWidth
127932             );
127933         } else {
127934             // anchored/calculated from the top
127935             prevConstrainRegion = Ext.create('Ext.util.Region',
127936                 prevBox.y + (prevCmp.minHeight || defaultMin),
127937                 prevBox.right,
127938                 // Bottom boundary is y + maxHeight if there IS a maxHeight.
127939                 // Otherwise it is calculated based upon the minWidth of the next Component
127940                 (prevCmp.maxHeight ? prevBox.y + prevCmp.maxHeight : nextBox.bottom - (nextCmp.minHeight || defaultMin)) + splitWidth,
127941                 prevBox.x
127942             );
127943             // anchored/calculated from the bottom
127944             nextConstrainRegion = Ext.create('Ext.util.Region',
127945                 // Top boundary is bottom - maxHeight if there IS a maxHeight.
127946                 // Otherwise it is calculated based upon the minHeight of the previous Component
127947                 (nextCmp.maxHeight ? nextBox.bottom - nextCmp.maxHeight : prevBox.y + (prevCmp.minHeight || defaultMin)) - splitWidth,
127948                 nextBox.right,
127949                 nextBox.bottom - (nextCmp.minHeight || defaultMin),
127950                 nextBox.x
127951             );
127952         }
127953
127954         // intersection of the two regions to provide region draggable
127955         return prevConstrainRegion.intersect(nextConstrainRegion);
127956     },
127957
127958     // Performs the actual resizing of the previous and next components
127959     performResize: function(e) {
127960         var me       = this,
127961             offset   = me.getOffset('dragTarget'),
127962             splitter = me.getSplitter(),
127963             orient   = splitter.orientation,
127964             prevCmp  = me.getPrevCmp(),
127965             nextCmp  = me.getNextCmp(),
127966             owner    = splitter.ownerCt,
127967             layout   = owner.getLayout();
127968
127969         // Inhibit automatic container layout caused by setSize calls below.
127970         owner.suspendLayout = true;
127971
127972         if (orient === 'vertical') {
127973             if (prevCmp) {
127974                 if (!prevCmp.maintainFlex) {
127975                     delete prevCmp.flex;
127976                     prevCmp.setSize(me.prevBox.width + offset[0], prevCmp.getHeight());
127977                 }
127978             }
127979             if (nextCmp) {
127980                 if (!nextCmp.maintainFlex) {
127981                     delete nextCmp.flex;
127982                     nextCmp.setSize(me.nextBox.width - offset[0], nextCmp.getHeight());
127983                 }
127984             }
127985         // verticals
127986         } else {
127987             if (prevCmp) {
127988                 if (!prevCmp.maintainFlex) {
127989                     delete prevCmp.flex;
127990                     prevCmp.setSize(prevCmp.getWidth(), me.prevBox.height + offset[1]);
127991                 }
127992             }
127993             if (nextCmp) {
127994                 if (!nextCmp.maintainFlex) {
127995                     delete nextCmp.flex;
127996                     nextCmp.setSize(prevCmp.getWidth(), me.nextBox.height - offset[1]);
127997                 }
127998             }
127999         }
128000         delete owner.suspendLayout;
128001         layout.onLayout();
128002     },
128003
128004     // Cleans up the overlay (if we have one) and calls the base. This cannot be done in
128005     // onEnd, because onEnd is only called if a drag is detected but the overlay is created
128006     // regardless (by onBeforeStart).
128007     endDrag: function () {
128008         var me = this;
128009
128010         if (me.overlay) {
128011              me.overlay.remove();
128012              delete me.overlay;
128013         }
128014
128015         me.callParent(arguments); // this calls onEnd
128016     },
128017
128018     // perform the resize and remove the proxy class from the splitter el
128019     onEnd: function(e) {
128020         var me = this,
128021             splitter = me.getSplitter();
128022             
128023         splitter.removeCls(splitter.baseCls + '-active');
128024         me.performResize();
128025     },
128026
128027     // Track the proxy and set the proper XY coordinates
128028     // while constraining the drag
128029     onDrag: function(e) {
128030         var me        = this,
128031             offset    = me.getOffset('dragTarget'),
128032             splitter  = me.getSplitter(),
128033             splitEl   = splitter.getEl(),
128034             orient    = splitter.orientation;
128035
128036         if (orient === "vertical") {
128037             splitEl.setX(me.startRegion.left + offset[0]);
128038         } else {
128039             splitEl.setY(me.startRegion.top + offset[1]);
128040         }
128041     },
128042
128043     getSplitter: function() {
128044         return Ext.getCmp(this.getDragCt().id);
128045     }
128046 });
128047 /**
128048  * @class Ext.selection.CellModel
128049  * @extends Ext.selection.Model
128050  */
128051 Ext.define('Ext.selection.CellModel', {
128052     extend: 'Ext.selection.Model',
128053     alias: 'selection.cellmodel',
128054     requires: ['Ext.util.KeyNav'],
128055
128056     /**
128057      * @cfg {Boolean} enableKeyNav
128058      * Turns on/off keyboard navigation within the grid.
128059      */
128060     enableKeyNav: true,
128061
128062     /**
128063      * @cfg {Boolean} preventWrap
128064      * Set this configuration to true to prevent wrapping around of selection as
128065      * a user navigates to the first or last column.
128066      */
128067     preventWrap: false,
128068
128069     constructor: function(){
128070         this.addEvents(
128071             /**
128072              * @event deselect
128073              * Fired after a cell is deselected
128074              * @param {Ext.selection.CellModel} this
128075              * @param {Ext.data.Model} record The record of the deselected cell
128076              * @param {Number} row The row index deselected
128077              * @param {Number} column The column index deselected
128078              */
128079             'deselect',
128080
128081             /**
128082              * @event select
128083              * Fired after a cell is selected
128084              * @param {Ext.selection.CellModel} this
128085              * @param {Ext.data.Model} record The record of the selected cell
128086              * @param {Number} row The row index selected
128087              * @param {Number} column The column index selected
128088              */
128089             'select'
128090         );
128091         this.callParent(arguments);
128092     },
128093
128094     bindComponent: function(view) {
128095         var me = this;
128096         me.primaryView = view;
128097         me.views = me.views || [];
128098         me.views.push(view);
128099         me.bind(view.getStore(), true);
128100
128101         view.on({
128102             cellmousedown: me.onMouseDown,
128103             refresh: me.onViewRefresh,
128104             scope: me
128105         });
128106
128107         if (me.enableKeyNav) {
128108             me.initKeyNav(view);
128109         }
128110     },
128111
128112     initKeyNav: function(view) {
128113         var me = this;
128114
128115         if (!view.rendered) {
128116             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
128117             return;
128118         }
128119
128120         view.el.set({
128121             tabIndex: -1
128122         });
128123
128124         // view.el has tabIndex -1 to allow for
128125         // keyboard events to be passed to it.
128126         me.keyNav = Ext.create('Ext.util.KeyNav', view.el, {
128127             up: me.onKeyUp,
128128             down: me.onKeyDown,
128129             right: me.onKeyRight,
128130             left: me.onKeyLeft,
128131             tab: me.onKeyTab,
128132             scope: me
128133         });
128134     },
128135
128136     getHeaderCt: function() {
128137         return this.primaryView.headerCt;
128138     },
128139
128140     onKeyUp: function(e, t) {
128141         this.move('up', e);
128142     },
128143
128144     onKeyDown: function(e, t) {
128145         this.move('down', e);
128146     },
128147
128148     onKeyLeft: function(e, t) {
128149         this.move('left', e);
128150     },
128151
128152     onKeyRight: function(e, t) {
128153         this.move('right', e);
128154     },
128155
128156     move: function(dir, e) {
128157         var me = this,
128158             pos = me.primaryView.walkCells(me.getCurrentPosition(), dir, e, me.preventWrap);
128159         if (pos) {
128160             me.setCurrentPosition(pos);
128161         }
128162         return pos;
128163     },
128164
128165     /**
128166      * Returns the current position in the format {row: row, column: column}
128167      */
128168     getCurrentPosition: function() {
128169         return this.position;
128170     },
128171
128172     /**
128173      * Sets the current position
128174      * @param {Object} position The position to set.
128175      */
128176     setCurrentPosition: function(pos) {
128177         var me = this;
128178
128179         if (me.position) {
128180             me.onCellDeselect(me.position);
128181         }
128182         if (pos) {
128183             me.onCellSelect(pos);
128184         }
128185         me.position = pos;
128186     },
128187
128188     /**
128189      * Set the current position based on where the user clicks.
128190      * @private
128191      */
128192     onMouseDown: function(view, cell, cellIndex, record, row, rowIndex, e) {
128193         this.setCurrentPosition({
128194             row: rowIndex,
128195             column: cellIndex
128196         });
128197     },
128198
128199     // notify the view that the cell has been selected to update the ui
128200     // appropriately and bring the cell into focus
128201     onCellSelect: function(position) {
128202         var me = this,
128203             store = me.view.getStore(),
128204             record = store.getAt(position.row);
128205
128206         me.doSelect(record);
128207         me.primaryView.onCellSelect(position);
128208         // TODO: Remove temporary cellFocus call here.
128209         me.primaryView.onCellFocus(position);
128210         me.fireEvent('select', me, record, position.row, position.column);
128211     },
128212
128213     // notify view that the cell has been deselected to update the ui
128214     // appropriately
128215     onCellDeselect: function(position) {
128216         var me = this,
128217             store = me.view.getStore(),
128218             record = store.getAt(position.row);
128219
128220         me.doDeselect(record);
128221         me.primaryView.onCellDeselect(position);
128222         me.fireEvent('deselect', me, record, position.row, position.column);
128223     },
128224
128225     onKeyTab: function(e, t) {
128226         var me = this,
128227             direction = e.shiftKey ? 'left' : 'right',
128228             editingPlugin = me.view.editingPlugin,
128229             position = me.move(direction, e);
128230
128231         if (editingPlugin && position && me.wasEditing) {
128232             editingPlugin.startEditByPosition(position);
128233         }
128234         delete me.wasEditing;
128235     },
128236
128237     onEditorTab: function(editingPlugin, e) {
128238         var me = this,
128239             direction = e.shiftKey ? 'left' : 'right',
128240             position  = me.move(direction, e);
128241
128242         if (position) {
128243             editingPlugin.startEditByPosition(position);
128244             me.wasEditing = true;
128245         }
128246     },
128247
128248     refresh: function() {
128249         var pos = this.getCurrentPosition();
128250         if (pos) {
128251             this.onCellSelect(pos);
128252         }
128253     },
128254
128255     onViewRefresh: function() {
128256         var pos = this.getCurrentPosition();
128257         if (pos) {
128258             this.onCellDeselect(pos);
128259             this.setCurrentPosition(null);
128260         }
128261     },
128262
128263     selectByPosition: function(position) {
128264         this.setCurrentPosition(position);
128265     }
128266 });
128267 /**
128268  * @class Ext.selection.RowModel
128269  * @extends Ext.selection.Model
128270  */
128271 Ext.define('Ext.selection.RowModel', {
128272     extend: 'Ext.selection.Model',
128273     alias: 'selection.rowmodel',
128274     requires: ['Ext.util.KeyNav'],
128275
128276     /**
128277      * @private
128278      * Number of pixels to scroll to the left/right when pressing
128279      * left/right keys.
128280      */
128281     deltaScroll: 5,
128282
128283     /**
128284      * @cfg {Boolean} enableKeyNav
128285      *
128286      * Turns on/off keyboard navigation within the grid.
128287      */
128288     enableKeyNav: true,
128289     
128290     /**
128291      * @cfg {Boolean} [ignoreRightMouseSelection=true]
128292      * True to ignore selections that are made when using the right mouse button if there are
128293      * records that are already selected. If no records are selected, selection will continue 
128294      * as normal
128295      */
128296     ignoreRightMouseSelection: true,
128297
128298     constructor: function(){
128299         this.addEvents(
128300             /**
128301              * @event beforedeselect
128302              * Fired before a record is deselected. If any listener returns false, the
128303              * deselection is cancelled.
128304              * @param {Ext.selection.RowModel} this
128305              * @param {Ext.data.Model} record The deselected record
128306              * @param {Number} index The row index deselected
128307              */
128308             'beforedeselect',
128309
128310             /**
128311              * @event beforeselect
128312              * Fired before a record is selected. If any listener returns false, the
128313              * selection is cancelled.
128314              * @param {Ext.selection.RowModel} this
128315              * @param {Ext.data.Model} record The selected record
128316              * @param {Number} index The row index selected
128317              */
128318             'beforeselect',
128319
128320             /**
128321              * @event deselect
128322              * Fired after a record is deselected
128323              * @param {Ext.selection.RowModel} this
128324              * @param {Ext.data.Model} record The deselected record
128325              * @param {Number} index The row index deselected
128326              */
128327             'deselect',
128328
128329             /**
128330              * @event select
128331              * Fired after a record is selected
128332              * @param {Ext.selection.RowModel} this
128333              * @param {Ext.data.Model} record The selected record
128334              * @param {Number} index The row index selected
128335              */
128336             'select'
128337         );
128338         this.callParent(arguments);
128339     },
128340
128341     bindComponent: function(view) {
128342         var me = this;
128343
128344         me.views = me.views || [];
128345         me.views.push(view);
128346         me.bind(view.getStore(), true);
128347
128348         view.on({
128349             itemmousedown: me.onRowMouseDown,
128350             scope: me
128351         });
128352
128353         if (me.enableKeyNav) {
128354             me.initKeyNav(view);
128355         }
128356     },
128357
128358     initKeyNav: function(view) {
128359         var me = this;
128360
128361         if (!view.rendered) {
128362             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
128363             return;
128364         }
128365
128366         view.el.set({
128367             tabIndex: -1
128368         });
128369
128370         // view.el has tabIndex -1 to allow for
128371         // keyboard events to be passed to it.
128372         me.keyNav = new Ext.util.KeyNav(view.el, {
128373             up: me.onKeyUp,
128374             down: me.onKeyDown,
128375             right: me.onKeyRight,
128376             left: me.onKeyLeft,
128377             pageDown: me.onKeyPageDown,
128378             pageUp: me.onKeyPageUp,
128379             home: me.onKeyHome,
128380             end: me.onKeyEnd,
128381             scope: me
128382         });
128383         view.el.on(Ext.EventManager.getKeyEvent(), me.onKeyPress, me);
128384     },
128385
128386     // Returns the number of rows currently visible on the screen or
128387     // false if there were no rows. This assumes that all rows are
128388     // of the same height and the first view is accurate.
128389     getRowsVisible: function() {
128390         var rowsVisible = false,
128391             view = this.views[0],
128392             row = view.getNode(0),
128393             rowHeight, gridViewHeight;
128394
128395         if (row) {
128396             rowHeight = Ext.fly(row).getHeight();
128397             gridViewHeight = view.el.getHeight();
128398             rowsVisible = Math.floor(gridViewHeight / rowHeight);
128399         }
128400
128401         return rowsVisible;
128402     },
128403
128404     // go to last visible record in grid.
128405     onKeyEnd: function(e, t) {
128406         var me = this,
128407             last = me.store.getAt(me.store.getCount() - 1);
128408
128409         if (last) {
128410             if (e.shiftKey) {
128411                 me.selectRange(last, me.lastFocused || 0);
128412                 me.setLastFocused(last);
128413             } else if (e.ctrlKey) {
128414                 me.setLastFocused(last);
128415             } else {
128416                 me.doSelect(last);
128417             }
128418         }
128419     },
128420
128421     // go to first visible record in grid.
128422     onKeyHome: function(e, t) {
128423         var me = this,
128424             first = me.store.getAt(0);
128425
128426         if (first) {
128427             if (e.shiftKey) {
128428                 me.selectRange(first, me.lastFocused || 0);
128429                 me.setLastFocused(first);
128430             } else if (e.ctrlKey) {
128431                 me.setLastFocused(first);
128432             } else {
128433                 me.doSelect(first, false);
128434             }
128435         }
128436     },
128437
128438     // Go one page up from the lastFocused record in the grid.
128439     onKeyPageUp: function(e, t) {
128440         var me = this,
128441             rowsVisible = me.getRowsVisible(),
128442             selIdx,
128443             prevIdx,
128444             prevRecord,
128445             currRec;
128446
128447         if (rowsVisible) {
128448             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
128449             prevIdx = selIdx - rowsVisible;
128450             if (prevIdx < 0) {
128451                 prevIdx = 0;
128452             }
128453             prevRecord = me.store.getAt(prevIdx);
128454             if (e.shiftKey) {
128455                 currRec = me.store.getAt(selIdx);
128456                 me.selectRange(prevRecord, currRec, e.ctrlKey, 'up');
128457                 me.setLastFocused(prevRecord);
128458             } else if (e.ctrlKey) {
128459                 e.preventDefault();
128460                 me.setLastFocused(prevRecord);
128461             } else {
128462                 me.doSelect(prevRecord);
128463             }
128464
128465         }
128466     },
128467
128468     // Go one page down from the lastFocused record in the grid.
128469     onKeyPageDown: function(e, t) {
128470         var me = this,
128471             rowsVisible = me.getRowsVisible(),
128472             selIdx,
128473             nextIdx,
128474             nextRecord,
128475             currRec;
128476
128477         if (rowsVisible) {
128478             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
128479             nextIdx = selIdx + rowsVisible;
128480             if (nextIdx >= me.store.getCount()) {
128481                 nextIdx = me.store.getCount() - 1;
128482             }
128483             nextRecord = me.store.getAt(nextIdx);
128484             if (e.shiftKey) {
128485                 currRec = me.store.getAt(selIdx);
128486                 me.selectRange(nextRecord, currRec, e.ctrlKey, 'down');
128487                 me.setLastFocused(nextRecord);
128488             } else if (e.ctrlKey) {
128489                 // some browsers, this means go thru browser tabs
128490                 // attempt to stop.
128491                 e.preventDefault();
128492                 me.setLastFocused(nextRecord);
128493             } else {
128494                 me.doSelect(nextRecord);
128495             }
128496         }
128497     },
128498
128499     // Select/Deselect based on pressing Spacebar.
128500     // Assumes a SIMPLE selectionmode style
128501     onKeyPress: function(e, t) {
128502         if (e.getKey() === e.SPACE) {
128503             e.stopEvent();
128504             var me = this,
128505                 record = me.lastFocused;
128506
128507             if (record) {
128508                 if (me.isSelected(record)) {
128509                     me.doDeselect(record, false);
128510                 } else {
128511                     me.doSelect(record, true);
128512                 }
128513             }
128514         }
128515     },
128516
128517     // Navigate one record up. This could be a selection or
128518     // could be simply focusing a record for discontiguous
128519     // selection. Provides bounds checking.
128520     onKeyUp: function(e, t) {
128521         var me = this,
128522             view = me.views[0],
128523             idx  = me.store.indexOf(me.lastFocused),
128524             record;
128525
128526         if (idx > 0) {
128527             // needs to be the filtered count as thats what
128528             // will be visible.
128529             record = me.store.getAt(idx - 1);
128530             if (e.shiftKey && me.lastFocused) {
128531                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
128532                     me.doDeselect(me.lastFocused, true);
128533                     me.setLastFocused(record);
128534                 } else if (!me.isSelected(me.lastFocused)) {
128535                     me.doSelect(me.lastFocused, true);
128536                     me.doSelect(record, true);
128537                 } else {
128538                     me.doSelect(record, true);
128539                 }
128540             } else if (e.ctrlKey) {
128541                 me.setLastFocused(record);
128542             } else {
128543                 me.doSelect(record);
128544                 //view.focusRow(idx - 1);
128545             }
128546         }
128547         // There was no lastFocused record, and the user has pressed up
128548         // Ignore??
128549         //else if (this.selected.getCount() == 0) {
128550         //
128551         //    this.doSelect(record);
128552         //    //view.focusRow(idx - 1);
128553         //}
128554     },
128555
128556     // Navigate one record down. This could be a selection or
128557     // could be simply focusing a record for discontiguous
128558     // selection. Provides bounds checking.
128559     onKeyDown: function(e, t) {
128560         var me = this,
128561             view = me.views[0],
128562             idx  = me.store.indexOf(me.lastFocused),
128563             record;
128564
128565         // needs to be the filtered count as thats what
128566         // will be visible.
128567         if (idx + 1 < me.store.getCount()) {
128568             record = me.store.getAt(idx + 1);
128569             if (me.selected.getCount() === 0) {
128570                 me.doSelect(record);
128571                 //view.focusRow(idx + 1);
128572             } else if (e.shiftKey && me.lastFocused) {
128573                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
128574                     me.doDeselect(me.lastFocused, true);
128575                     me.setLastFocused(record);
128576                 } else if (!me.isSelected(me.lastFocused)) {
128577                     me.doSelect(me.lastFocused, true);
128578                     me.doSelect(record, true);
128579                 } else {
128580                     me.doSelect(record, true);
128581                 }
128582             } else if (e.ctrlKey) {
128583                 me.setLastFocused(record);
128584             } else {
128585                 me.doSelect(record);
128586                 //view.focusRow(idx + 1);
128587             }
128588         }
128589     },
128590
128591     scrollByDeltaX: function(delta) {
128592         var view    = this.views[0],
128593             section = view.up(),
128594             hScroll = section.horizontalScroller;
128595
128596         if (hScroll) {
128597             hScroll.scrollByDeltaX(delta);
128598         }
128599     },
128600
128601     onKeyLeft: function(e, t) {
128602         this.scrollByDeltaX(-this.deltaScroll);
128603     },
128604
128605     onKeyRight: function(e, t) {
128606         this.scrollByDeltaX(this.deltaScroll);
128607     },
128608
128609     // Select the record with the event included so that
128610     // we can take into account ctrlKey, shiftKey, etc
128611     onRowMouseDown: function(view, record, item, index, e) {
128612         view.el.focus();
128613         if (!this.allowRightMouseSelection(e)) {
128614             return;
128615         }
128616         this.selectWithEvent(record, e);
128617     },
128618     
128619     /**
128620      * Checks whether a selection should proceed based on the ignoreRightMouseSelection
128621      * option.
128622      * @private
128623      * @param {Ext.EventObject} e The event
128624      * @return {Boolean} False if the selection should not proceed
128625      */
128626     allowRightMouseSelection: function(e) {
128627         var disallow = this.ignoreRightMouseSelection && e.button !== 0;
128628         if (disallow) {
128629             disallow = this.hasSelection();
128630         }
128631         return !disallow;
128632     },
128633
128634     // Allow the GridView to update the UI by
128635     // adding/removing a CSS class from the row.
128636     onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
128637         var me      = this,
128638             views   = me.views,
128639             viewsLn = views.length,
128640             store   = me.store,
128641             rowIdx  = store.indexOf(record),
128642             eventName = isSelected ? 'select' : 'deselect',
128643             i = 0;
128644
128645         if ((suppressEvent || me.fireEvent('before' + eventName, me, record, rowIdx)) !== false &&
128646                 commitFn() !== false) {
128647
128648             for (; i < viewsLn; i++) {
128649                 if (isSelected) {
128650                     views[i].onRowSelect(rowIdx, suppressEvent);
128651                 } else {
128652                     views[i].onRowDeselect(rowIdx, suppressEvent);
128653                 }
128654             }
128655
128656             if (!suppressEvent) {
128657                 me.fireEvent(eventName, me, record, rowIdx);
128658             }
128659         }
128660     },
128661
128662     // Provide indication of what row was last focused via
128663     // the gridview.
128664     onLastFocusChanged: function(oldFocused, newFocused, supressFocus) {
128665         var views   = this.views,
128666             viewsLn = views.length,
128667             store   = this.store,
128668             rowIdx,
128669             i = 0;
128670
128671         if (oldFocused) {
128672             rowIdx = store.indexOf(oldFocused);
128673             if (rowIdx != -1) {
128674                 for (; i < viewsLn; i++) {
128675                     views[i].onRowFocus(rowIdx, false);
128676                 }
128677             }
128678         }
128679
128680         if (newFocused) {
128681             rowIdx = store.indexOf(newFocused);
128682             if (rowIdx != -1) {
128683                 for (i = 0; i < viewsLn; i++) {
128684                     views[i].onRowFocus(rowIdx, true, supressFocus);
128685                 }
128686             }
128687         }
128688     },
128689
128690     onEditorTab: function(editingPlugin, e) {
128691         var me = this,
128692             view = me.views[0],
128693             record = editingPlugin.getActiveRecord(),
128694             header = editingPlugin.getActiveColumn(),
128695             position = view.getPosition(record, header),
128696             direction = e.shiftKey ? 'left' : 'right',
128697             newPosition  = view.walkCells(position, direction, e, this.preventWrap);
128698
128699         if (newPosition) {
128700             editingPlugin.startEditByPosition(newPosition);
128701         }
128702     },
128703
128704     selectByPosition: function(position) {
128705         var record = this.store.getAt(position.row);
128706         this.select(record);
128707     }
128708 });
128709 /**
128710  * @class Ext.selection.CheckboxModel
128711  * @extends Ext.selection.RowModel
128712  *
128713  * A selection model that renders a column of checkboxes that can be toggled to
128714  * select or deselect rows. The default mode for this selection model is MULTI.
128715  *
128716  * The selection model will inject a header for the checkboxes in the first view
128717  * and according to the 'injectCheckbox' configuration.
128718  */
128719 Ext.define('Ext.selection.CheckboxModel', {
128720     alias: 'selection.checkboxmodel',
128721     extend: 'Ext.selection.RowModel',
128722
128723     /**
128724      * @cfg {String} mode
128725      * Modes of selection.
128726      * Valid values are SINGLE, SIMPLE, and MULTI. Defaults to 'MULTI'
128727      */
128728     mode: 'MULTI',
128729
128730     /**
128731      * @cfg {Number/Boolean/String} injectCheckbox
128732      * Instructs the SelectionModel whether or not to inject the checkbox header
128733      * automatically or not. (Note: By not placing the checkbox in manually, the
128734      * grid view will need to be rendered 2x on initial render.)
128735      * Supported values are a Number index, false and the strings 'first' and 'last'.
128736      */
128737     injectCheckbox: 0,
128738
128739     /**
128740      * @cfg {Boolean} checkOnly <tt>true</tt> if rows can only be selected by clicking on the
128741      * checkbox column.
128742      */
128743     checkOnly: false,
128744
128745     headerWidth: 24,
128746
128747     // private
128748     checkerOnCls: Ext.baseCSSPrefix + 'grid-hd-checker-on',
128749
128750     bindComponent: function(view) {
128751         var me = this;
128752
128753         me.sortable = false;
128754         me.callParent(arguments);
128755         if (!me.hasLockedHeader() || view.headerCt.lockedCt) {
128756             // if we have a locked header, only hook up to the first
128757             view.headerCt.on('headerclick', me.onHeaderClick, me);
128758             me.addCheckbox(true);
128759             me.mon(view.ownerCt, 'reconfigure', me.addCheckbox, me);
128760         }
128761     },
128762
128763     hasLockedHeader: function(){
128764         var hasLocked = false;
128765         Ext.each(this.views, function(view){
128766             if (view.headerCt.lockedCt) {
128767                 hasLocked = true;
128768                 return false;
128769             }
128770         });
128771         return hasLocked;
128772     },
128773
128774     /**
128775      * Add the header checkbox to the header row
128776      * @private
128777      * @param {Boolean} initial True if we're binding for the first time.
128778      */
128779     addCheckbox: function(initial){
128780         var me = this,
128781             checkbox = me.injectCheckbox,
128782             view = me.views[0],
128783             headerCt = view.headerCt;
128784
128785         if (checkbox !== false) {
128786             if (checkbox == 'first') {
128787                 checkbox = 0;
128788             } else if (checkbox == 'last') {
128789                 checkbox = headerCt.getColumnCount();
128790             }
128791             headerCt.add(checkbox,  me.getHeaderConfig());
128792         }
128793
128794         if (initial !== true) {
128795             view.refresh();
128796         }
128797     },
128798
128799     /**
128800      * Toggle the ui header between checked and unchecked state.
128801      * @param {Boolean} isChecked
128802      * @private
128803      */
128804     toggleUiHeader: function(isChecked) {
128805         var view     = this.views[0],
128806             headerCt = view.headerCt,
128807             checkHd  = headerCt.child('gridcolumn[isCheckerHd]');
128808
128809         if (checkHd) {
128810             if (isChecked) {
128811                 checkHd.el.addCls(this.checkerOnCls);
128812             } else {
128813                 checkHd.el.removeCls(this.checkerOnCls);
128814             }
128815         }
128816     },
128817
128818     /**
128819      * Toggle between selecting all and deselecting all when clicking on
128820      * a checkbox header.
128821      */
128822     onHeaderClick: function(headerCt, header, e) {
128823         if (header.isCheckerHd) {
128824             e.stopEvent();
128825             var isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');
128826             if (isChecked) {
128827                 // We have to supress the event or it will scrollTo the change
128828                 this.deselectAll(true);
128829             } else {
128830                 // We have to supress the event or it will scrollTo the change
128831                 this.selectAll(true);
128832             }
128833         }
128834     },
128835
128836     /**
128837      * Retrieve a configuration to be used in a HeaderContainer.
128838      * This should be used when injectCheckbox is set to false.
128839      */
128840     getHeaderConfig: function() {
128841         var me = this;
128842
128843         return {
128844             isCheckerHd: true,
128845             text : '&#160;',
128846             width: me.headerWidth,
128847             sortable: false,
128848             draggable: false,
128849             resizable: false,
128850             hideable: false,
128851             menuDisabled: true,
128852             dataIndex: '',
128853             cls: Ext.baseCSSPrefix + 'column-header-checkbox ',
128854             renderer: Ext.Function.bind(me.renderer, me),
128855             locked: me.hasLockedHeader()
128856         };
128857     },
128858
128859     /**
128860      * Generates the HTML to be rendered in the injected checkbox column for each row.
128861      * Creates the standard checkbox markup by default; can be overridden to provide custom rendering.
128862      * See {@link Ext.grid.column.Column#renderer} for description of allowed parameters.
128863      */
128864     renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
128865         metaData.tdCls = Ext.baseCSSPrefix + 'grid-cell-special';
128866         return '<div class="' + Ext.baseCSSPrefix + 'grid-row-checker">&#160;</div>';
128867     },
128868
128869     // override
128870     onRowMouseDown: function(view, record, item, index, e) {
128871         view.el.focus();
128872         var me = this,
128873             checker = e.getTarget('.' + Ext.baseCSSPrefix + 'grid-row-checker');
128874             
128875         if (!me.allowRightMouseSelection(e)) {
128876             return;
128877         }
128878
128879         // checkOnly set, but we didn't click on a checker.
128880         if (me.checkOnly && !checker) {
128881             return;
128882         }
128883
128884         if (checker) {
128885             var mode = me.getSelectionMode();
128886             // dont change the mode if its single otherwise
128887             // we would get multiple selection
128888             if (mode !== 'SINGLE') {
128889                 me.setSelectionMode('SIMPLE');
128890             }
128891             me.selectWithEvent(record, e);
128892             me.setSelectionMode(mode);
128893         } else {
128894             me.selectWithEvent(record, e);
128895         }
128896     },
128897
128898     /**
128899      * Synchronize header checker value as selection changes.
128900      * @private
128901      */
128902     onSelectChange: function() {
128903         this.callParent(arguments);
128904
128905         // check to see if all records are selected
128906         var hdSelectStatus = this.selected.getCount() === this.store.getCount();
128907         this.toggleUiHeader(hdSelectStatus);
128908     }
128909 });
128910
128911 /**
128912  * @class Ext.selection.TreeModel
128913  * @extends Ext.selection.RowModel
128914  *
128915  * Adds custom behavior for left/right keyboard navigation for use with a tree.
128916  * Depends on the view having an expand and collapse method which accepts a
128917  * record.
128918  * 
128919  * @private
128920  */
128921 Ext.define('Ext.selection.TreeModel', {
128922     extend: 'Ext.selection.RowModel',
128923     alias: 'selection.treemodel',
128924     
128925     // typically selection models prune records from the selection
128926     // model when they are removed, because the TreeView constantly
128927     // adds/removes records as they are expanded/collapsed
128928     pruneRemoved: false,
128929     
128930     onKeyRight: function(e, t) {
128931         var focused = this.getLastFocused(),
128932             view    = this.view;
128933             
128934         if (focused) {
128935             // tree node is already expanded, go down instead
128936             // this handles both the case where we navigate to firstChild and if
128937             // there are no children to the nextSibling
128938             if (focused.isExpanded()) {
128939                 this.onKeyDown(e, t);
128940             // if its not a leaf node, expand it
128941             } else if (!focused.isLeaf()) {
128942                 view.expand(focused);
128943             }
128944         }
128945     },
128946     
128947     onKeyLeft: function(e, t) {
128948         var focused = this.getLastFocused(),
128949             view    = this.view,
128950             viewSm  = view.getSelectionModel(),
128951             parentNode, parentRecord;
128952
128953         if (focused) {
128954             parentNode = focused.parentNode;
128955             // if focused node is already expanded, collapse it
128956             if (focused.isExpanded()) {
128957                 view.collapse(focused);
128958             // has a parentNode and its not root
128959             // TODO: this needs to cover the case where the root isVisible
128960             } else if (parentNode && !parentNode.isRoot()) {
128961                 // Select a range of records when doing multiple selection.
128962                 if (e.shiftKey) {
128963                     viewSm.selectRange(parentNode, focused, e.ctrlKey, 'up');
128964                     viewSm.setLastFocused(parentNode);
128965                 // just move focus, not selection
128966                 } else if (e.ctrlKey) {
128967                     viewSm.setLastFocused(parentNode);
128968                 // select it
128969                 } else {
128970                     viewSm.select(parentNode);
128971                 }
128972             }
128973         }
128974     },
128975     
128976     onKeyPress: function(e, t) {
128977         var key = e.getKey(),
128978             selected, 
128979             checked;
128980         
128981         if (key === e.SPACE || key === e.ENTER) {
128982             e.stopEvent();
128983             selected = this.getLastSelected();
128984             if (selected) {
128985                 this.view.onCheckChange(selected);
128986             }
128987         } else {
128988             this.callParent(arguments);
128989         }
128990     }
128991 });
128992
128993 /**
128994  * @class Ext.slider.Thumb
128995  * @extends Ext.Base
128996  * @private
128997  * Represents a single thumb element on a Slider. This would not usually be created manually and would instead
128998  * be created internally by an {@link Ext.slider.Multi Multi slider}.
128999  */
129000 Ext.define('Ext.slider.Thumb', {
129001     requires: ['Ext.dd.DragTracker', 'Ext.util.Format'],
129002     /**
129003      * @private
129004      * @property {Number} topThumbZIndex
129005      * The number used internally to set the z index of the top thumb (see promoteThumb for details)
129006      */
129007     topZIndex: 10000,
129008
129009     /**
129010      * @cfg {Ext.slider.MultiSlider} slider (required)
129011      * The Slider to render to.
129012      */
129013
129014     /**
129015      * Creates new slider thumb.
129016      * @param {Object} config (optional) Config object.
129017      */
129018     constructor: function(config) {
129019         var me = this;
129020
129021         /**
129022          * @property {Ext.slider.MultiSlider} slider
129023          * The slider this thumb is contained within
129024          */
129025         Ext.apply(me, config || {}, {
129026             cls: Ext.baseCSSPrefix + 'slider-thumb',
129027
129028             /**
129029              * @cfg {Boolean} constrain True to constrain the thumb so that it cannot overlap its siblings
129030              */
129031             constrain: false
129032         });
129033         me.callParent([config]);
129034
129035         if (me.slider.vertical) {
129036             Ext.apply(me, Ext.slider.Thumb.Vertical);
129037         }
129038     },
129039
129040     /**
129041      * Renders the thumb into a slider
129042      */
129043     render: function() {
129044         var me = this;
129045
129046         me.el = me.slider.innerEl.insertFirst({cls: me.cls});
129047         if (me.disabled) {
129048             me.disable();
129049         }
129050         me.initEvents();
129051     },
129052
129053     /**
129054      * @private
129055      * move the thumb
129056      */
129057     move: function(v, animate){
129058         if(!animate){
129059             this.el.setLeft(v);
129060         }else{
129061             Ext.create('Ext.fx.Anim', {
129062                 target: this.el,
129063                 duration: 350,
129064                 to: {
129065                     left: v
129066                 }
129067             });
129068         }
129069     },
129070
129071     /**
129072      * @private
129073      * Bring thumb dom element to front.
129074      */
129075     bringToFront: function() {
129076         this.el.setStyle('zIndex', this.topZIndex);
129077     },
129078
129079     /**
129080      * @private
129081      * Send thumb dom element to back.
129082      */
129083     sendToBack: function() {
129084         this.el.setStyle('zIndex', '');
129085     },
129086
129087     /**
129088      * Enables the thumb if it is currently disabled
129089      */
129090     enable: function() {
129091         var me = this;
129092
129093         me.disabled = false;
129094         if (me.el) {
129095             me.el.removeCls(me.slider.disabledCls);
129096         }
129097     },
129098
129099     /**
129100      * Disables the thumb if it is currently enabled
129101      */
129102     disable: function() {
129103         var me = this;
129104
129105         me.disabled = true;
129106         if (me.el) {
129107             me.el.addCls(me.slider.disabledCls);
129108         }
129109     },
129110
129111     /**
129112      * Sets up an Ext.dd.DragTracker for this thumb
129113      */
129114     initEvents: function() {
129115         var me = this,
129116             el = me.el;
129117
129118         me.tracker = Ext.create('Ext.dd.DragTracker', {
129119             onBeforeStart: Ext.Function.bind(me.onBeforeDragStart, me),
129120             onStart      : Ext.Function.bind(me.onDragStart, me),
129121             onDrag       : Ext.Function.bind(me.onDrag, me),
129122             onEnd        : Ext.Function.bind(me.onDragEnd, me),
129123             tolerance    : 3,
129124             autoStart    : 300,
129125             overCls      : Ext.baseCSSPrefix + 'slider-thumb-over'
129126         });
129127
129128         me.tracker.initEl(el);
129129     },
129130
129131     /**
129132      * @private
129133      * This is tied into the internal Ext.dd.DragTracker. If the slider is currently disabled,
129134      * this returns false to disable the DragTracker too.
129135      * @return {Boolean} False if the slider is currently disabled
129136      */
129137     onBeforeDragStart : function(e) {
129138         if (this.disabled) {
129139             return false;
129140         } else {
129141             this.slider.promoteThumb(this);
129142             return true;
129143         }
129144     },
129145
129146     /**
129147      * @private
129148      * This is tied into the internal Ext.dd.DragTracker's onStart template method. Adds the drag CSS class
129149      * to the thumb and fires the 'dragstart' event
129150      */
129151     onDragStart: function(e){
129152         var me = this;
129153
129154         me.el.addCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
129155         me.dragging = true;
129156         me.dragStartValue = me.value;
129157
129158         me.slider.fireEvent('dragstart', me.slider, e, me);
129159     },
129160
129161     /**
129162      * @private
129163      * This is tied into the internal Ext.dd.DragTracker's onDrag template method. This is called every time
129164      * the DragTracker detects a drag movement. It updates the Slider's value using the position of the drag
129165      */
129166     onDrag: function(e) {
129167         var me       = this,
129168             slider   = me.slider,
129169             index    = me.index,
129170             newValue = me.getNewValue(),
129171             above,
129172             below;
129173
129174         if (me.constrain) {
129175             above = slider.thumbs[index + 1];
129176             below = slider.thumbs[index - 1];
129177
129178             if (below !== undefined && newValue <= below.value) {
129179                 newValue = below.value;
129180             }
129181
129182             if (above !== undefined && newValue >= above.value) {
129183                 newValue = above.value;
129184             }
129185         }
129186
129187         slider.setValue(index, newValue, false);
129188         slider.fireEvent('drag', slider, e, me);
129189     },
129190
129191     getNewValue: function() {
129192         var slider = this.slider,
129193             pos = slider.innerEl.translatePoints(this.tracker.getXY());
129194
129195         return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision);
129196     },
129197
129198     /**
129199      * @private
129200      * This is tied to the internal Ext.dd.DragTracker's onEnd template method. Removes the drag CSS class and
129201      * fires the 'changecomplete' event with the new value
129202      */
129203     onDragEnd: function(e) {
129204         var me     = this,
129205             slider = me.slider,
129206             value  = me.value;
129207
129208         me.el.removeCls(Ext.baseCSSPrefix + 'slider-thumb-drag');
129209
129210         me.dragging = false;
129211         slider.fireEvent('dragend', slider, e);
129212
129213         if (me.dragStartValue != value) {
129214             slider.fireEvent('changecomplete', slider, value, me);
129215         }
129216     },
129217
129218     destroy: function() {
129219         Ext.destroy(this.tracker);
129220     },
129221     statics: {
129222         // Method overrides to support vertical dragging of thumb within slider
129223         Vertical: {
129224             getNewValue: function() {
129225                 var slider   = this.slider,
129226                     innerEl  = slider.innerEl,
129227                     pos      = innerEl.translatePoints(this.tracker.getXY()),
129228                     bottom   = innerEl.getHeight() - pos.top;
129229
129230                 return Ext.util.Format.round(slider.reverseValue(bottom), slider.decimalPrecision);
129231             },
129232             move: function(v, animate) {
129233                 if (!animate) {
129234                     this.el.setBottom(v);
129235                 } else {
129236                     Ext.create('Ext.fx.Anim', {
129237                         target: this.el,
129238                         duration: 350,
129239                         to: {
129240                             bottom: v
129241                         }
129242                     });
129243                 }
129244             }
129245         }
129246     }
129247 });
129248
129249 /**
129250  * Simple plugin for using an Ext.tip.Tip with a slider to show the slider value. In general this class is not created
129251  * directly, instead pass the {@link Ext.slider.Multi#useTips} and {@link Ext.slider.Multi#tipText} configuration
129252  * options to the slider directly.
129253  *
129254  *     @example
129255  *     Ext.create('Ext.slider.Single', {
129256  *         width: 214,
129257  *         minValue: 0,
129258  *         maxValue: 100,
129259  *         useTips: true,
129260  *         renderTo: Ext.getBody()
129261  *     });
129262  *
129263  * Optionally provide your own tip text by passing tipText:
129264  *
129265  *     @example
129266  *     Ext.create('Ext.slider.Single', {
129267  *         width: 214,
129268  *         minValue: 0,
129269  *         maxValue: 100,
129270  *         useTips: true,
129271  *         tipText: function(thumb){
129272  *             return Ext.String.format('**{0}% complete**', thumb.value);
129273  *         },
129274  *         renderTo: Ext.getBody()
129275  *     });
129276  */
129277 Ext.define('Ext.slider.Tip', {
129278     extend: 'Ext.tip.Tip',
129279     minWidth: 10,
129280     alias: 'widget.slidertip',
129281     offsets : [0, -10],
129282
129283     isSliderTip: true,
129284
129285     init: function(slider) {
129286         var me = this;
129287
129288         slider.on({
129289             scope    : me,
129290             dragstart: me.onSlide,
129291             drag     : me.onSlide,
129292             dragend  : me.hide,
129293             destroy  : me.destroy
129294         });
129295     },
129296     /**
129297      * @private
129298      * Called whenever a dragstart or drag event is received on the associated Thumb.
129299      * Aligns the Tip with the Thumb's new position.
129300      * @param {Ext.slider.MultiSlider} slider The slider
129301      * @param {Ext.EventObject} e The Event object
129302      * @param {Ext.slider.Thumb} thumb The thumb that the Tip is attached to
129303      */
129304     onSlide : function(slider, e, thumb) {
129305         var me = this;
129306         me.show();
129307         me.update(me.getText(thumb));
129308         me.doComponentLayout();
129309         me.el.alignTo(thumb.el, 'b-t?', me.offsets);
129310     },
129311
129312     /**
129313      * Used to create the text that appears in the Tip's body. By default this just returns the value of the Slider
129314      * Thumb that the Tip is attached to. Override to customize.
129315      * @param {Ext.slider.Thumb} thumb The Thumb that the Tip is attached to
129316      * @return {String} The text to display in the tip
129317      */
129318     getText : function(thumb) {
129319         return String(thumb.value);
129320     }
129321 });
129322 /**
129323  * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
129324  * and animation. Can be added as an item to any container.
129325  *
129326  * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
129327  *
129328  *     @example
129329  *     Ext.create('Ext.slider.Multi', {
129330  *         width: 200,
129331  *         values: [25, 50, 75],
129332  *         increment: 5,
129333  *         minValue: 0,
129334  *         maxValue: 100,
129335  *
129336  *         // this defaults to true, setting to false allows the thumbs to pass each other
129337  *         constrainThumbs: false,
129338  *         renderTo: Ext.getBody()
129339  *     });
129340  */
129341 Ext.define('Ext.slider.Multi', {
129342     extend: 'Ext.form.field.Base',
129343     alias: 'widget.multislider',
129344     alternateClassName: 'Ext.slider.MultiSlider',
129345
129346     requires: [
129347         'Ext.slider.Thumb',
129348         'Ext.slider.Tip',
129349         'Ext.Number',
129350         'Ext.util.Format',
129351         'Ext.Template',
129352         'Ext.layout.component.field.Slider'
129353     ],
129354
129355     // note: {id} here is really {inputId}, but {cmpId} is available
129356     fieldSubTpl: [
129357         '<div id="{id}" class="' + Ext.baseCSSPrefix + 'slider {fieldCls} {vertical}" aria-valuemin="{minValue}" aria-valuemax="{maxValue}" aria-valuenow="{value}" aria-valuetext="{value}">',
129358             '<div id="{cmpId}-endEl" class="' + Ext.baseCSSPrefix + 'slider-end" role="presentation">',
129359                 '<div id="{cmpId}-innerEl" class="' + Ext.baseCSSPrefix + 'slider-inner" role="presentation">',
129360                     '<a id="{cmpId}-focusEl" class="' + Ext.baseCSSPrefix + 'slider-focus" href="#" tabIndex="-1" hidefocus="on" role="presentation"></a>',
129361                 '</div>',
129362             '</div>',
129363         '</div>',
129364         {
129365             disableFormats: true,
129366             compiled: true
129367         }
129368     ],
129369
129370     /**
129371      * @cfg {Number} value
129372      * A value with which to initialize the slider. Defaults to minValue. Setting this will only result in the creation
129373      * of a single slider thumb; if you want multiple thumbs then use the {@link #values} config instead.
129374      */
129375
129376     /**
129377      * @cfg {Number[]} values
129378      * Array of Number values with which to initalize the slider. A separate slider thumb will be created for each value
129379      * in this array. This will take precedence over the single {@link #value} config.
129380      */
129381
129382     /**
129383      * @cfg {Boolean} vertical
129384      * Orient the Slider vertically rather than horizontally.
129385      */
129386     vertical: false,
129387
129388     /**
129389      * @cfg {Number} minValue
129390      * The minimum value for the Slider.
129391      */
129392     minValue: 0,
129393
129394     /**
129395      * @cfg {Number} maxValue
129396      * The maximum value for the Slider.
129397      */
129398     maxValue: 100,
129399
129400     /**
129401      * @cfg {Number/Boolean} decimalPrecision The number of decimal places to which to round the Slider's value.
129402      *
129403      * To disable rounding, configure as **false**.
129404      */
129405     decimalPrecision: 0,
129406
129407     /**
129408      * @cfg {Number} keyIncrement
129409      * How many units to change the Slider when adjusting with keyboard navigation. If the increment
129410      * config is larger, it will be used instead.
129411      */
129412     keyIncrement: 1,
129413
129414     /**
129415      * @cfg {Number} increment
129416      * How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
129417      */
129418     increment: 0,
129419
129420     /**
129421      * @private
129422      * @property {Number[]} clickRange
129423      * 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],
129424      * 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'
129425      * 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
129426      */
129427     clickRange: [5,15],
129428
129429     /**
129430      * @cfg {Boolean} clickToChange
129431      * Determines whether or not clicking on the Slider axis will change the slider.
129432      */
129433     clickToChange : true,
129434
129435     /**
129436      * @cfg {Boolean} animate
129437      * Turn on or off animation.
129438      */
129439     animate: true,
129440
129441     /**
129442      * @property {Boolean} dragging
129443      * True while the thumb is in a drag operation
129444      */
129445     dragging: false,
129446
129447     /**
129448      * @cfg {Boolean} constrainThumbs
129449      * True to disallow thumbs from overlapping one another.
129450      */
129451     constrainThumbs: true,
129452
129453     componentLayout: 'sliderfield',
129454
129455     /**
129456      * @cfg {Boolean} useTips
129457      * True to use an Ext.slider.Tip to display tips for the value.
129458      */
129459     useTips : true,
129460
129461     /**
129462      * @cfg {Function} tipText
129463      * A function used to display custom text for the slider tip. Defaults to null, which will use the default on the
129464      * plugin.
129465      */
129466     tipText : null,
129467
129468     ariaRole: 'slider',
129469
129470     // private override
129471     initValue: function() {
129472         var me = this,
129473             extValue = Ext.value,
129474             // Fallback for initial values: values config -> value config -> minValue config -> 0
129475             values = extValue(me.values, [extValue(me.value, extValue(me.minValue, 0))]),
129476             i = 0,
129477             len = values.length;
129478
129479         // Store for use in dirty check
129480         me.originalValue = values;
129481
129482         // Add a thumb for each value
129483         for (; i < len; i++) {
129484             me.addThumb(values[i]);
129485         }
129486     },
129487
129488     // private override
129489     initComponent : function() {
129490         var me = this,
129491             tipPlug,
129492             hasTip;
129493
129494         /**
129495          * @property {Array} thumbs
129496          * Array containing references to each thumb
129497          */
129498         me.thumbs = [];
129499
129500         me.keyIncrement = Math.max(me.increment, me.keyIncrement);
129501
129502         me.addEvents(
129503             /**
129504              * @event beforechange
129505              * Fires before the slider value is changed. By returning false from an event handler, you can cancel the
129506              * event and prevent the slider from changing.
129507              * @param {Ext.slider.Multi} slider The slider
129508              * @param {Number} newValue The new value which the slider is being changed to.
129509              * @param {Number} oldValue The old value which the slider was previously.
129510              */
129511             'beforechange',
129512
129513             /**
129514              * @event change
129515              * Fires when the slider value is changed.
129516              * @param {Ext.slider.Multi} slider The slider
129517              * @param {Number} newValue The new value which the slider has been changed to.
129518              * @param {Ext.slider.Thumb} thumb The thumb that was changed
129519              */
129520             'change',
129521
129522             /**
129523              * @event changecomplete
129524              * Fires when the slider value is changed by the user and any drag operations have completed.
129525              * @param {Ext.slider.Multi} slider The slider
129526              * @param {Number} newValue The new value which the slider has been changed to.
129527              * @param {Ext.slider.Thumb} thumb The thumb that was changed
129528              */
129529             'changecomplete',
129530
129531             /**
129532              * @event dragstart
129533              * Fires after a drag operation has started.
129534              * @param {Ext.slider.Multi} slider The slider
129535              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
129536              */
129537             'dragstart',
129538
129539             /**
129540              * @event drag
129541              * Fires continuously during the drag operation while the mouse is moving.
129542              * @param {Ext.slider.Multi} slider The slider
129543              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
129544              */
129545             'drag',
129546
129547             /**
129548              * @event dragend
129549              * Fires after the drag operation has completed.
129550              * @param {Ext.slider.Multi} slider The slider
129551              * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
129552              */
129553             'dragend'
129554         );
129555
129556         if (me.vertical) {
129557             Ext.apply(me, Ext.slider.Multi.Vertical);
129558         }
129559
129560         me.callParent();
129561
129562         // only can use it if it exists.
129563         if (me.useTips) {
129564             tipPlug = me.tipText ? {getText: me.tipText} : {};
129565             me.plugins = me.plugins || [];
129566             Ext.each(me.plugins, function(plug){
129567                 if (plug.isSliderTip) {
129568                     hasTip = true;
129569                     return false;
129570                 }
129571             });
129572             if (!hasTip) {
129573                 me.plugins.push(Ext.create('Ext.slider.Tip', tipPlug));
129574             }
129575         }
129576     },
129577
129578     /**
129579      * Creates a new thumb and adds it to the slider
129580      * @param {Number} value The initial value to set on the thumb. Defaults to 0
129581      * @return {Ext.slider.Thumb} The thumb
129582      */
129583     addThumb: function(value) {
129584         var me = this,
129585             thumb = Ext.create('Ext.slider.Thumb', {
129586             value    : value,
129587             slider   : me,
129588             index    : me.thumbs.length,
129589             constrain: me.constrainThumbs
129590         });
129591         me.thumbs.push(thumb);
129592
129593         //render the thumb now if needed
129594         if (me.rendered) {
129595             thumb.render();
129596         }
129597
129598         return thumb;
129599     },
129600
129601     /**
129602      * @private
129603      * Moves the given thumb above all other by increasing its z-index. This is called when as drag
129604      * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
129605      * required when the thumbs are stacked on top of each other at one of the ends of the slider's
129606      * range, which can result in the user not being able to move any of them.
129607      * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
129608      */
129609     promoteThumb: function(topThumb) {
129610         var thumbs = this.thumbs,
129611             ln = thumbs.length,
129612             zIndex, thumb, i;
129613
129614         for (i = 0; i < ln; i++) {
129615             thumb = thumbs[i];
129616
129617             if (thumb == topThumb) {
129618                 thumb.bringToFront();
129619             } else {
129620                 thumb.sendToBack();
129621             }
129622         }
129623     },
129624
129625     // private override
129626     onRender : function() {
129627         var me = this,
129628             i = 0,
129629             thumbs = me.thumbs,
129630             len = thumbs.length,
129631             thumb;
129632
129633         Ext.applyIf(me.subTplData, {
129634             vertical: me.vertical ? Ext.baseCSSPrefix + 'slider-vert' : Ext.baseCSSPrefix + 'slider-horz',
129635             minValue: me.minValue,
129636             maxValue: me.maxValue,
129637             value: me.value
129638         });
129639
129640         me.addChildEls('endEl', 'innerEl', 'focusEl');
129641
129642         me.callParent(arguments);
129643
129644         //render each thumb
129645         for (; i < len; i++) {
129646             thumbs[i].render();
129647         }
129648
129649         //calculate the size of half a thumb
129650         thumb = me.innerEl.down('.' + Ext.baseCSSPrefix + 'slider-thumb');
129651         me.halfThumb = (me.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
129652
129653     },
129654
129655     /**
129656      * Utility method to set the value of the field when the slider changes.
129657      * @param {Object} slider The slider object.
129658      * @param {Object} v The new value.
129659      * @private
129660      */
129661     onChange : function(slider, v) {
129662         this.setValue(v, undefined, true);
129663     },
129664
129665     /**
129666      * @private
129667      * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
129668      */
129669     initEvents : function() {
129670         var me = this;
129671
129672         me.mon(me.el, {
129673             scope    : me,
129674             mousedown: me.onMouseDown,
129675             keydown  : me.onKeyDown,
129676             change : me.onChange
129677         });
129678
129679         me.focusEl.swallowEvent("click", true);
129680     },
129681
129682     /**
129683      * @private
129684      * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
129685      * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
129686      * @param {Ext.EventObject} e The click event
129687      */
129688     onMouseDown : function(e) {
129689         var me = this,
129690             thumbClicked = false,
129691             i = 0,
129692             thumbs = me.thumbs,
129693             len = thumbs.length,
129694             local;
129695
129696         if (me.disabled) {
129697             return;
129698         }
129699
129700         //see if the click was on any of the thumbs
129701         for (; i < len; i++) {
129702             thumbClicked = thumbClicked || e.target == thumbs[i].el.dom;
129703         }
129704
129705         if (me.clickToChange && !thumbClicked) {
129706             local = me.innerEl.translatePoints(e.getXY());
129707             me.onClickChange(local);
129708         }
129709         me.focus();
129710     },
129711
129712     /**
129713      * @private
129714      * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Multi.Vertical.
129715      * Only changes the value if the click was within this.clickRange.
129716      * @param {Object} local Object containing top and left values for the click event.
129717      */
129718     onClickChange : function(local) {
129719         var me = this,
129720             thumb, index;
129721
129722         if (local.top > me.clickRange[0] && local.top < me.clickRange[1]) {
129723             //find the nearest thumb to the click event
129724             thumb = me.getNearest(local, 'left');
129725             if (!thumb.disabled) {
129726                 index = thumb.index;
129727                 me.setValue(index, Ext.util.Format.round(me.reverseValue(local.left), me.decimalPrecision), undefined, true);
129728             }
129729         }
129730     },
129731
129732     /**
129733      * @private
129734      * Returns the nearest thumb to a click event, along with its distance
129735      * @param {Object} local Object containing top and left values from a click event
129736      * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
129737      * @return {Object} The closest thumb object and its distance from the click event
129738      */
129739     getNearest: function(local, prop) {
129740         var me = this,
129741             localValue = prop == 'top' ? me.innerEl.getHeight() - local[prop] : local[prop],
129742             clickValue = me.reverseValue(localValue),
129743             nearestDistance = (me.maxValue - me.minValue) + 5, //add a small fudge for the end of the slider
129744             index = 0,
129745             nearest = null,
129746             thumbs = me.thumbs,
129747             i = 0,
129748             len = thumbs.length,
129749             thumb,
129750             value,
129751             dist;
129752
129753         for (; i < len; i++) {
129754             thumb = me.thumbs[i];
129755             value = thumb.value;
129756             dist  = Math.abs(value - clickValue);
129757
129758             if (Math.abs(dist <= nearestDistance)) {
129759                 nearest = thumb;
129760                 index = i;
129761                 nearestDistance = dist;
129762             }
129763         }
129764         return nearest;
129765     },
129766
129767     /**
129768      * @private
129769      * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right
129770      * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction
129771      * @param {Ext.EventObject} e The Event object
129772      */
129773     onKeyDown : function(e) {
129774         /*
129775          * The behaviour for keyboard handling with multiple thumbs is currently undefined.
129776          * There's no real sane default for it, so leave it like this until we come up
129777          * with a better way of doing it.
129778          */
129779         var me = this,
129780             k,
129781             val;
129782
129783         if(me.disabled || me.thumbs.length !== 1) {
129784             e.preventDefault();
129785             return;
129786         }
129787         k = e.getKey();
129788
129789         switch(k) {
129790             case e.UP:
129791             case e.RIGHT:
129792                 e.stopEvent();
129793                 val = e.ctrlKey ? me.maxValue : me.getValue(0) + me.keyIncrement;
129794                 me.setValue(0, val, undefined, true);
129795             break;
129796             case e.DOWN:
129797             case e.LEFT:
129798                 e.stopEvent();
129799                 val = e.ctrlKey ? me.minValue : me.getValue(0) - me.keyIncrement;
129800                 me.setValue(0, val, undefined, true);
129801             break;
129802             default:
129803                 e.preventDefault();
129804         }
129805     },
129806
129807     // private
129808     afterRender : function() {
129809         var me = this,
129810             i = 0,
129811             thumbs = me.thumbs,
129812             len = thumbs.length,
129813             thumb,
129814             v;
129815
129816         me.callParent(arguments);
129817
129818         for (; i < len; i++) {
129819             thumb = thumbs[i];
129820
129821             if (thumb.value !== undefined) {
129822                 v = me.normalizeValue(thumb.value);
129823                 if (v !== thumb.value) {
129824                     // delete this.value;
129825                     me.setValue(i, v, false);
129826                 } else {
129827                     thumb.move(me.translateValue(v), false);
129828                 }
129829             }
129830         }
129831     },
129832
129833     /**
129834      * @private
129835      * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
129836      * the ratio is 2
129837      * @return {Number} The ratio of pixels to mapped values
129838      */
129839     getRatio : function() {
129840         var w = this.innerEl.getWidth(),
129841             v = this.maxValue - this.minValue;
129842         return v === 0 ? w : (w/v);
129843     },
129844
129845     /**
129846      * @private
129847      * Returns a snapped, constrained value when given a desired value
129848      * @param {Number} value Raw number value
129849      * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
129850      */
129851     normalizeValue : function(v) {
129852         var me = this;
129853
129854         v = Ext.Number.snap(v, this.increment, this.minValue, this.maxValue);
129855         v = Ext.util.Format.round(v, me.decimalPrecision);
129856         v = Ext.Number.constrain(v, me.minValue, me.maxValue);
129857         return v;
129858     },
129859
129860     /**
129861      * Sets the minimum value for the slider instance. If the current value is less than the minimum value, the current
129862      * value will be changed.
129863      * @param {Number} val The new minimum value
129864      */
129865     setMinValue : function(val) {
129866         var me = this,
129867             i = 0,
129868             thumbs = me.thumbs,
129869             len = thumbs.length,
129870             t;
129871
129872         me.minValue = val;
129873         if (me.rendered) {
129874             me.inputEl.dom.setAttribute('aria-valuemin', val);
129875         }
129876
129877         for (; i < len; ++i) {
129878             t = thumbs[i];
129879             t.value = t.value < val ? val : t.value;
129880         }
129881         me.syncThumbs();
129882     },
129883
129884     /**
129885      * Sets the maximum value for the slider instance. If the current value is more than the maximum value, the current
129886      * value will be changed.
129887      * @param {Number} val The new maximum value
129888      */
129889     setMaxValue : function(val) {
129890         var me = this,
129891             i = 0,
129892             thumbs = me.thumbs,
129893             len = thumbs.length,
129894             t;
129895
129896         me.maxValue = val;
129897         if (me.rendered) {
129898             me.inputEl.dom.setAttribute('aria-valuemax', val);
129899         }
129900
129901         for (; i < len; ++i) {
129902             t = thumbs[i];
129903             t.value = t.value > val ? val : t.value;
129904         }
129905         me.syncThumbs();
129906     },
129907
129908     /**
129909      * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
129910      * maxValue.
129911      * @param {Number} index Index of the thumb to move
129912      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
129913      * @param {Boolean} [animate=true] Turn on or off animation
129914      */
129915     setValue : function(index, value, animate, changeComplete) {
129916         var me = this,
129917             thumb = me.thumbs[index];
129918
129919         // ensures value is contstrained and snapped
129920         value = me.normalizeValue(value);
129921
129922         if (value !== thumb.value && me.fireEvent('beforechange', me, value, thumb.value, thumb) !== false) {
129923             thumb.value = value;
129924             if (me.rendered) {
129925                 // TODO this only handles a single value; need a solution for exposing multiple values to aria.
129926                 // Perhaps this should go on each thumb element rather than the outer element.
129927                 me.inputEl.set({
129928                     'aria-valuenow': value,
129929                     'aria-valuetext': value
129930                 });
129931
129932                 thumb.move(me.translateValue(value), Ext.isDefined(animate) ? animate !== false : me.animate);
129933
129934                 me.fireEvent('change', me, value, thumb);
129935                 if (changeComplete) {
129936                     me.fireEvent('changecomplete', me, value, thumb);
129937                 }
129938             }
129939         }
129940     },
129941
129942     /**
129943      * @private
129944      */
129945     translateValue : function(v) {
129946         var ratio = this.getRatio();
129947         return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
129948     },
129949
129950     /**
129951      * @private
129952      * Given a pixel location along the slider, returns the mapped slider value for that pixel.
129953      * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reverseValue(50)
129954      * returns 200
129955      * @param {Number} pos The position along the slider to return a mapped value for
129956      * @return {Number} The mapped value for the given position
129957      */
129958     reverseValue : function(pos) {
129959         var ratio = this.getRatio();
129960         return (pos + (this.minValue * ratio)) / ratio;
129961     },
129962
129963     // private
129964     focus : function() {
129965         this.focusEl.focus(10);
129966     },
129967
129968     //private
129969     onDisable: function() {
129970         var me = this,
129971             i = 0,
129972             thumbs = me.thumbs,
129973             len = thumbs.length,
129974             thumb,
129975             el,
129976             xy;
129977
129978         me.callParent();
129979
129980         for (; i < len; i++) {
129981             thumb = thumbs[i];
129982             el = thumb.el;
129983
129984             thumb.disable();
129985
129986             if(Ext.isIE) {
129987                 //IE breaks when using overflow visible and opacity other than 1.
129988                 //Create a place holder for the thumb and display it.
129989                 xy = el.getXY();
129990                 el.hide();
129991
129992                 me.innerEl.addCls(me.disabledCls).dom.disabled = true;
129993
129994                 if (!me.thumbHolder) {
129995                     me.thumbHolder = me.endEl.createChild({cls: Ext.baseCSSPrefix + 'slider-thumb ' + me.disabledCls});
129996                 }
129997
129998                 me.thumbHolder.show().setXY(xy);
129999             }
130000         }
130001     },
130002
130003     //private
130004     onEnable: function() {
130005         var me = this,
130006             i = 0,
130007             thumbs = me.thumbs,
130008             len = thumbs.length,
130009             thumb,
130010             el;
130011
130012         this.callParent();
130013
130014         for (; i < len; i++) {
130015             thumb = thumbs[i];
130016             el = thumb.el;
130017
130018             thumb.enable();
130019
130020             if (Ext.isIE) {
130021                 me.innerEl.removeCls(me.disabledCls).dom.disabled = false;
130022
130023                 if (me.thumbHolder) {
130024                     me.thumbHolder.hide();
130025                 }
130026
130027                 el.show();
130028                 me.syncThumbs();
130029             }
130030         }
130031     },
130032
130033     /**
130034      * Synchronizes thumbs position to the proper proportion of the total component width based on the current slider
130035      * {@link #value}. This will be called automatically when the Slider is resized by a layout, but if it is rendered
130036      * auto width, this method can be called from another resize handler to sync the Slider if necessary.
130037      */
130038     syncThumbs : function() {
130039         if (this.rendered) {
130040             var thumbs = this.thumbs,
130041                 length = thumbs.length,
130042                 i = 0;
130043
130044             for (; i < length; i++) {
130045                 thumbs[i].move(this.translateValue(thumbs[i].value));
130046             }
130047         }
130048     },
130049
130050     /**
130051      * Returns the current value of the slider
130052      * @param {Number} index The index of the thumb to return a value for
130053      * @return {Number/Number[]} The current value of the slider at the given index, or an array of all thumb values if
130054      * no index is given.
130055      */
130056     getValue : function(index) {
130057         return Ext.isNumber(index) ? this.thumbs[index].value : this.getValues();
130058     },
130059
130060     /**
130061      * Returns an array of values - one for the location of each thumb
130062      * @return {Number[]} The set of thumb values
130063      */
130064     getValues: function() {
130065         var values = [],
130066             i = 0,
130067             thumbs = this.thumbs,
130068             len = thumbs.length;
130069
130070         for (; i < len; i++) {
130071             values.push(thumbs[i].value);
130072         }
130073
130074         return values;
130075     },
130076
130077     getSubmitValue: function() {
130078         var me = this;
130079         return (me.disabled || !me.submitValue) ? null : me.getValue();
130080     },
130081
130082     reset: function() {
130083         var me = this,
130084             Array = Ext.Array;
130085         Array.forEach(Array.from(me.originalValue), function(val, i) {
130086             me.setValue(i, val);
130087         });
130088         me.clearInvalid();
130089         // delete here so we reset back to the original state
130090         delete me.wasValid;
130091     },
130092
130093     // private
130094     beforeDestroy : function() {
130095         var me = this;
130096
130097         Ext.destroy(me.innerEl, me.endEl, me.focusEl);
130098         Ext.each(me.thumbs, function(thumb) {
130099             Ext.destroy(thumb);
130100         }, me);
130101
130102         me.callParent();
130103     },
130104
130105     statics: {
130106         // Method overrides to support slider with vertical orientation
130107         Vertical: {
130108             getRatio : function() {
130109                 var h = this.innerEl.getHeight(),
130110                     v = this.maxValue - this.minValue;
130111                 return h/v;
130112             },
130113
130114             onClickChange : function(local) {
130115                 var me = this,
130116                     thumb, index, bottom;
130117
130118                 if (local.left > me.clickRange[0] && local.left < me.clickRange[1]) {
130119                     thumb = me.getNearest(local, 'top');
130120                     if (!thumb.disabled) {
130121                         index = thumb.index;
130122                         bottom =  me.reverseValue(me.innerEl.getHeight() - local.top);
130123
130124                         me.setValue(index, Ext.util.Format.round(me.minValue + bottom, me.decimalPrecision), undefined, true);
130125                     }
130126                 }
130127             }
130128         }
130129     }
130130 });
130131
130132 /**
130133  * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
130134  * and animation. Can be added as an item to any container.
130135  *
130136  *     @example
130137  *     Ext.create('Ext.slider.Single', {
130138  *         width: 200,
130139  *         value: 50,
130140  *         increment: 10,
130141  *         minValue: 0,
130142  *         maxValue: 100,
130143  *         renderTo: Ext.getBody()
130144  *     });
130145  *
130146  * The class Ext.slider.Single is aliased to Ext.Slider for backwards compatibility.
130147  */
130148 Ext.define('Ext.slider.Single', {
130149     extend: 'Ext.slider.Multi',
130150     alias: ['widget.slider', 'widget.sliderfield'],
130151     alternateClassName: ['Ext.Slider', 'Ext.form.SliderField', 'Ext.slider.SingleSlider', 'Ext.slider.Slider'],
130152
130153     /**
130154      * Returns the current value of the slider
130155      * @return {Number} The current value of the slider
130156      */
130157     getValue: function() {
130158         // just returns the value of the first thumb, which should be the only one in a single slider
130159         return this.callParent([0]);
130160     },
130161
130162     /**
130163      * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
130164      * maxValue.
130165      * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
130166      * @param {Boolean} [animate] Turn on or off animation
130167      */
130168     setValue: function(value, animate) {
130169         var args = Ext.toArray(arguments),
130170             len  = args.length;
130171
130172         // this is to maintain backwards compatiblity for sliders with only one thunb. Usually you must pass the thumb
130173         // index to setValue, but if we only have one thumb we inject the index here first if given the multi-slider
130174         // signature without the required index. The index will always be 0 for a single slider
130175         if (len == 1 || (len <= 3 && typeof arguments[1] != 'number')) {
130176             args.unshift(0);
130177         }
130178
130179         return this.callParent(args);
130180     },
130181
130182     // private
130183     getNearest : function(){
130184         // Since there's only 1 thumb, it's always the nearest
130185         return this.thumbs[0];
130186     }
130187 });
130188
130189 /**
130190  * @author Ed Spencer
130191  * @class Ext.tab.Tab
130192  * @extends Ext.button.Button
130193  *
130194  * <p>Represents a single Tab in a {@link Ext.tab.Panel TabPanel}. A Tab is simply a slightly customized {@link Ext.button.Button Button},
130195  * styled to look like a tab. Tabs are optionally closable, and can also be disabled. Typically you will not
130196  * need to create Tabs manually as the framework does so automatically when you use a {@link Ext.tab.Panel TabPanel}</p>
130197  */
130198 Ext.define('Ext.tab.Tab', {
130199     extend: 'Ext.button.Button',
130200     alias: 'widget.tab',
130201
130202     requires: [
130203         'Ext.layout.component.Tab',
130204         'Ext.util.KeyNav'
130205     ],
130206
130207     componentLayout: 'tab',
130208
130209     isTab: true,
130210
130211     baseCls: Ext.baseCSSPrefix + 'tab',
130212
130213     /**
130214      * @cfg {String} activeCls
130215      * The CSS class to be applied to a Tab when it is active.
130216      * Providing your own CSS for this class enables you to customize the active state.
130217      */
130218     activeCls: 'active',
130219
130220     /**
130221      * @cfg {String} disabledCls
130222      * The CSS class to be applied to a Tab when it is disabled.
130223      */
130224
130225     /**
130226      * @cfg {String} closableCls
130227      * The CSS class which is added to the tab when it is closable
130228      */
130229     closableCls: 'closable',
130230
130231     /**
130232      * @cfg {Boolean} closable True to make the Tab start closable (the close icon will be visible).
130233      */
130234     closable: true,
130235
130236     /**
130237      * @cfg {String} closeText
130238      * The accessible text label for the close button link; only used when {@link #closable} = true.
130239      */
130240     closeText: 'Close Tab',
130241
130242     /**
130243      * @property {Boolean} active
130244      * Read-only property indicating that this tab is currently active. This is NOT a public configuration.
130245      */
130246     active: false,
130247
130248     /**
130249      * @property closable
130250      * @type Boolean
130251      * True if the tab is currently closable
130252      */
130253
130254     scale: false,
130255
130256     position: 'top',
130257
130258     initComponent: function() {
130259         var me = this;
130260
130261         me.addEvents(
130262             /**
130263              * @event activate
130264              * Fired when the tab is activated.
130265              * @param {Ext.tab.Tab} this
130266              */
130267             'activate',
130268
130269             /**
130270              * @event deactivate
130271              * Fired when the tab is deactivated.
130272              * @param {Ext.tab.Tab} this
130273              */
130274             'deactivate',
130275
130276             /**
130277              * @event beforeclose
130278              * Fires if the user clicks on the Tab's close button, but before the {@link #close} event is fired. Return
130279              * false from any listener to stop the close event being fired
130280              * @param {Ext.tab.Tab} tab The Tab object
130281              */
130282             'beforeclose',
130283
130284             /**
130285              * @event close
130286              * Fires to indicate that the tab is to be closed, usually because the user has clicked the close button.
130287              * @param {Ext.tab.Tab} tab The Tab object
130288              */
130289             'close'
130290         );
130291
130292         me.callParent(arguments);
130293
130294         if (me.card) {
130295             me.setCard(me.card);
130296         }
130297     },
130298
130299     /**
130300      * @ignore
130301      */
130302     onRender: function() {
130303         var me = this,
130304             tabBar = me.up('tabbar'),
130305             tabPanel = me.up('tabpanel');
130306
130307         me.addClsWithUI(me.position);
130308
130309         // Set all the state classNames, as they need to include the UI
130310         // me.disabledCls = me.getClsWithUIs('disabled');
130311
130312         me.syncClosableUI();
130313
130314         // Propagate minTabWidth and maxTabWidth settings from the owning TabBar then TabPanel
130315         if (!me.minWidth) {
130316             me.minWidth = (tabBar) ? tabBar.minTabWidth : me.minWidth;
130317             if (!me.minWidth && tabPanel) {
130318                 me.minWidth = tabPanel.minTabWidth;
130319             }
130320             if (me.minWidth && me.iconCls) {
130321                 me.minWidth += 25;
130322             }
130323         }
130324         if (!me.maxWidth) {
130325             me.maxWidth = (tabBar) ? tabBar.maxTabWidth : me.maxWidth;
130326             if (!me.maxWidth && tabPanel) {
130327                 me.maxWidth = tabPanel.maxTabWidth;
130328             }
130329         }
130330
130331         me.callParent(arguments);
130332
130333         if (me.active) {
130334             me.activate(true);
130335         }
130336
130337         me.syncClosableElements();
130338
130339         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
130340             enter: me.onEnterKey,
130341             del: me.onDeleteKey,
130342             scope: me
130343         });
130344     },
130345
130346     // inherit docs
130347     enable : function(silent) {
130348         var me = this;
130349
130350         me.callParent(arguments);
130351
130352         me.removeClsWithUI(me.position + '-disabled');
130353
130354         return me;
130355     },
130356
130357     // inherit docs
130358     disable : function(silent) {
130359         var me = this;
130360
130361         me.callParent(arguments);
130362
130363         me.addClsWithUI(me.position + '-disabled');
130364
130365         return me;
130366     },
130367
130368     /**
130369      * @ignore
130370      */
130371     onDestroy: function() {
130372         var me = this;
130373
130374         if (me.closeEl) {
130375             me.closeEl.un('click', Ext.EventManager.preventDefault);
130376             me.closeEl = null;
130377         }
130378
130379         Ext.destroy(me.keyNav);
130380         delete me.keyNav;
130381
130382         me.callParent(arguments);
130383     },
130384
130385     /**
130386      * Sets the tab as either closable or not
130387      * @param {Boolean} closable Pass false to make the tab not closable. Otherwise the tab will be made closable (eg a
130388      * close button will appear on the tab)
130389      */
130390     setClosable: function(closable) {
130391         var me = this;
130392
130393         // Closable must be true if no args
130394         closable = (!arguments.length || !!closable);
130395
130396         if (me.closable != closable) {
130397             me.closable = closable;
130398
130399             // set property on the user-facing item ('card'):
130400             if (me.card) {
130401                 me.card.closable = closable;
130402             }
130403
130404             me.syncClosableUI();
130405
130406             if (me.rendered) {
130407                 me.syncClosableElements();
130408
130409                 // Tab will change width to accommodate close icon
130410                 me.doComponentLayout();
130411                 if (me.ownerCt) {
130412                     me.ownerCt.doLayout();
130413                 }
130414             }
130415         }
130416     },
130417
130418     /**
130419      * This method ensures that the closeBtn element exists or not based on 'closable'.
130420      * @private
130421      */
130422     syncClosableElements: function () {
130423         var me = this;
130424
130425         if (me.closable) {
130426             if (!me.closeEl) {
130427                 me.closeEl = me.el.createChild({
130428                     tag: 'a',
130429                     cls: me.baseCls + '-close-btn',
130430                     href: '#',
130431                     // html: me.closeText, // removed for EXTJSIV-1719, by rob@sencha.com
130432                     title: me.closeText
130433                 }).on('click', Ext.EventManager.preventDefault);  // mon ???
130434             }
130435         } else {
130436             var closeEl = me.closeEl;
130437             if (closeEl) {
130438                 closeEl.un('click', Ext.EventManager.preventDefault);
130439                 closeEl.remove();
130440                 me.closeEl = null;
130441             }
130442         }
130443     },
130444
130445     /**
130446      * This method ensures that the UI classes are added or removed based on 'closable'.
130447      * @private
130448      */
130449     syncClosableUI: function () {
130450         var me = this, classes = [me.closableCls, me.closableCls + '-' + me.position];
130451
130452         if (me.closable) {
130453             me.addClsWithUI(classes);
130454         } else {
130455             me.removeClsWithUI(classes);
130456         }
130457     },
130458
130459     /**
130460      * Sets this tab's attached card. Usually this is handled automatically by the {@link Ext.tab.Panel} that this Tab
130461      * belongs to and would not need to be done by the developer
130462      * @param {Ext.Component} card The card to set
130463      */
130464     setCard: function(card) {
130465         var me = this;
130466
130467         me.card = card;
130468         me.setText(me.title || card.title);
130469         me.setIconCls(me.iconCls || card.iconCls);
130470     },
130471
130472     /**
130473      * @private
130474      * Listener attached to click events on the Tab's close button
130475      */
130476     onCloseClick: function() {
130477         var me = this;
130478
130479         if (me.fireEvent('beforeclose', me) !== false) {
130480             if (me.tabBar) {
130481                 if (me.tabBar.closeTab(me) === false) {
130482                     // beforeclose on the panel vetoed the event, stop here
130483                     return;
130484                 }
130485             } else {
130486                 // if there's no tabbar, fire the close event
130487                 me.fireEvent('close', me);
130488             }
130489         }
130490     },
130491
130492     /**
130493      * Fires the close event on the tab.
130494      * @private
130495      */
130496     fireClose: function(){
130497         this.fireEvent('close', this);
130498     },
130499
130500     /**
130501      * @private
130502      */
130503     onEnterKey: function(e) {
130504         var me = this;
130505
130506         if (me.tabBar) {
130507             me.tabBar.onClick(e, me.el);
130508         }
130509     },
130510
130511    /**
130512      * @private
130513      */
130514     onDeleteKey: function(e) {
130515         var me = this;
130516
130517         if (me.closable) {
130518             me.onCloseClick();
130519         }
130520     },
130521
130522     // @private
130523     activate : function(supressEvent) {
130524         var me = this;
130525
130526         me.active = true;
130527         me.addClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
130528
130529         if (supressEvent !== true) {
130530             me.fireEvent('activate', me);
130531         }
130532     },
130533
130534     // @private
130535     deactivate : function(supressEvent) {
130536         var me = this;
130537
130538         me.active = false;
130539         me.removeClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
130540
130541         if (supressEvent !== true) {
130542             me.fireEvent('deactivate', me);
130543         }
130544     }
130545 });
130546
130547 /**
130548  * @author Ed Spencer
130549  * TabBar is used internally by a {@link Ext.tab.Panel TabPanel} and typically should not need to be created manually.
130550  * The tab bar automatically removes the default title provided by {@link Ext.panel.Header}
130551  */
130552 Ext.define('Ext.tab.Bar', {
130553     extend: 'Ext.panel.Header',
130554     alias: 'widget.tabbar',
130555     baseCls: Ext.baseCSSPrefix + 'tab-bar',
130556
130557     requires: [
130558         'Ext.tab.Tab',
130559         'Ext.FocusManager'
130560     ],
130561
130562     isTabBar: true,
130563     
130564     /**
130565      * @cfg {String} title @hide
130566      */
130567     
130568     /**
130569      * @cfg {String} iconCls @hide
130570      */
130571
130572     // @private
130573     defaultType: 'tab',
130574
130575     /**
130576      * @cfg {Boolean} plain
130577      * True to not show the full background on the tabbar
130578      */
130579     plain: false,
130580
130581     // @private
130582     renderTpl: [
130583         '<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>',
130584         '<div id="{id}-strip" class="{baseCls}-strip<tpl if="ui"> {baseCls}-strip-{ui}<tpl for="uiCls"> {parent.baseCls}-strip-{parent.ui}-{.}</tpl></tpl>"></div>'
130585     ],
130586
130587     /**
130588      * @cfg {Number} minTabWidth
130589      * The minimum width for a tab in this tab Bar. Defaults to the tab Panel's {@link Ext.tab.Panel#minTabWidth minTabWidth} value.
130590      * @deprecated This config is deprecated. It is much easier to use the {@link Ext.tab.Panel#minTabWidth minTabWidth} config on the TabPanel.
130591      */
130592
130593     /**
130594      * @cfg {Number} maxTabWidth
130595      * The maximum width for a tab in this tab Bar. Defaults to the tab Panel's {@link Ext.tab.Panel#maxTabWidth maxTabWidth} value.
130596      * @deprecated This config is deprecated. It is much easier to use the {@link Ext.tab.Panel#maxTabWidth maxTabWidth} config on the TabPanel.
130597      */
130598
130599     // @private
130600     initComponent: function() {
130601         var me = this,
130602             keys;
130603
130604         if (me.plain) {
130605             me.setUI(me.ui + '-plain');
130606         }
130607
130608         me.addClsWithUI(me.dock);
130609
130610         me.addEvents(
130611             /**
130612              * @event change
130613              * Fired when the currently-active tab has changed
130614              * @param {Ext.tab.Bar} tabBar The TabBar
130615              * @param {Ext.tab.Tab} tab The new Tab
130616              * @param {Ext.Component} card The card that was just shown in the TabPanel
130617              */
130618             'change'
130619         );
130620
130621         me.addChildEls('body', 'strip');
130622         me.callParent(arguments);
130623
130624         // TabBar must override the Header's align setting.
130625         me.layout.align = (me.orientation == 'vertical') ? 'left' : 'top';
130626         me.layout.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.Scroller', me.layout);
130627
130628         me.remove(me.titleCmp);
130629         delete me.titleCmp;
130630
130631         // Subscribe to Ext.FocusManager for key navigation
130632         keys = me.orientation == 'vertical' ? ['up', 'down'] : ['left', 'right'];
130633         Ext.FocusManager.subscribe(me, {
130634             keys: keys
130635         });
130636
130637         Ext.apply(me.renderData, {
130638             bodyCls: me.bodyCls
130639         });
130640     },
130641
130642     // @private
130643     onAdd: function(tab) {
130644         tab.position = this.dock;
130645         this.callParent(arguments);
130646     },
130647     
130648     onRemove: function(tab) {
130649         var me = this;
130650         
130651         if (tab === me.previousTab) {
130652             me.previousTab = null;
130653         }
130654         if (me.items.getCount() === 0) {
130655             me.activeTab = null;
130656         }
130657         me.callParent(arguments);    
130658     },
130659
130660     // @private
130661     afterRender: function() {
130662         var me = this;
130663
130664         me.mon(me.el, {
130665             scope: me,
130666             click: me.onClick,
130667             delegate: '.' + Ext.baseCSSPrefix + 'tab'
130668         });
130669         me.callParent(arguments);
130670
130671     },
130672
130673     afterComponentLayout : function() {
130674         var me = this;
130675
130676         me.callParent(arguments);
130677         me.strip.setWidth(me.el.getWidth());
130678     },
130679
130680     // @private
130681     onClick: function(e, target) {
130682         // The target might not be a valid tab el.
130683         var tab = Ext.getCmp(target.id),
130684             tabPanel = this.tabPanel;
130685
130686         target = e.getTarget();
130687
130688         if (tab && tab.isDisabled && !tab.isDisabled()) {
130689             if (tab.closable && target === tab.closeEl.dom) {
130690                 tab.onCloseClick();
130691             } else {
130692                 if (tabPanel) {
130693                     // TabPanel will card setActiveTab of the TabBar
130694                     tabPanel.setActiveTab(tab.card);
130695                 } else {
130696                     this.setActiveTab(tab);
130697                 }
130698                 tab.focus();
130699             }
130700         }
130701     },
130702
130703     /**
130704      * @private
130705      * Closes the given tab by removing it from the TabBar and removing the corresponding card from the TabPanel
130706      * @param {Ext.tab.Tab} tab The tab to close
130707      */
130708     closeTab: function(tab) {
130709         var me = this,
130710             card = tab.card,
130711             tabPanel = me.tabPanel,
130712             nextTab;
130713
130714         if (card && card.fireEvent('beforeclose', card) === false) {
130715             return false;
130716         }
130717
130718         if (tab.active && me.items.getCount() > 1) {
130719             nextTab = me.previousTab || tab.next('tab') || me.items.first();
130720             me.setActiveTab(nextTab);
130721             if (tabPanel) {
130722                 tabPanel.setActiveTab(nextTab.card);
130723             }
130724         }
130725         /*
130726          * force the close event to fire. By the time this function returns,
130727          * the tab is already destroyed and all listeners have been purged
130728          * so the tab can't fire itself.
130729          */
130730         tab.fireClose();
130731         me.remove(tab);
130732
130733         if (tabPanel && card) {
130734             card.fireEvent('close', card);
130735             tabPanel.remove(card);
130736         }
130737
130738         if (nextTab) {
130739             nextTab.focus();
130740         }
130741     },
130742
130743     /**
130744      * @private
130745      * Marks the given tab as active
130746      * @param {Ext.tab.Tab} tab The tab to mark active
130747      */
130748     setActiveTab: function(tab) {
130749         if (tab.disabled) {
130750             return;
130751         }
130752         var me = this;
130753         if (me.activeTab) {
130754             me.previousTab = me.activeTab;
130755             me.activeTab.deactivate();
130756         }
130757         tab.activate();
130758
130759         if (me.rendered) {
130760             me.layout.layout();
130761             tab.el && tab.el.scrollIntoView(me.layout.getRenderTarget());
130762         }
130763         me.activeTab = tab;
130764         me.fireEvent('change', me, tab, tab.card);
130765     }
130766 });
130767
130768 /**
130769  * @author Ed Spencer, Tommy Maintz, Brian Moeskau
130770  *
130771  * A basic tab container. TabPanels can be used exactly like a standard {@link Ext.panel.Panel} for
130772  * layout purposes, but also have special support for containing child Components
130773  * (`{@link Ext.container.Container#items items}`) that are managed using a
130774  * {@link Ext.layout.container.Card CardLayout layout manager}, and displayed as separate tabs.
130775  *
130776  * **Note:** By default, a tab's close tool _destroys_ the child tab Component and all its descendants.
130777  * This makes the child tab Component, and all its descendants **unusable**.  To enable re-use of a tab,
130778  * configure the TabPanel with `{@link #autoDestroy autoDestroy: false}`.
130779  *
130780  * ## TabPanel's layout
130781  *
130782  * TabPanels use a Dock layout to position the {@link Ext.tab.Bar TabBar} at the top of the widget.
130783  * Panels added to the TabPanel will have their header hidden by default because the Tab will
130784  * automatically take the Panel's configured title and icon.
130785  *
130786  * TabPanels use their {@link Ext.panel.Header header} or {@link Ext.panel.Panel#fbar footer}
130787  * element (depending on the {@link #tabPosition} configuration) to accommodate the tab selector buttons.
130788  * This means that a TabPanel will not display any configured title, and will not display any configured
130789  * header {@link Ext.panel.Panel#tools tools}.
130790  *
130791  * To display a header, embed the TabPanel in a {@link Ext.panel.Panel Panel} which uses
130792  * `{@link Ext.container.Container#layout layout: 'fit'}`.
130793  *
130794  * ## Controlling tabs
130795  *
130796  * Configuration options for the {@link Ext.tab.Tab} that represents the component can be passed in
130797  * by specifying the tabConfig option:
130798  *
130799  *     @example
130800  *     Ext.create('Ext.tab.Panel', {
130801  *         width: 400,
130802  *         height: 400,
130803  *         renderTo: document.body,
130804  *         items: [{
130805  *             title: 'Foo'
130806  *         }, {
130807  *             title: 'Bar',
130808  *             tabConfig: {
130809  *                 title: 'Custom Title',
130810  *                 tooltip: 'A button tooltip'
130811  *             }
130812  *         }]
130813  *     });
130814  *
130815  * # Examples
130816  *
130817  * Here is a basic TabPanel rendered to the body. This also shows the useful configuration {@link #activeTab},
130818  * which allows you to set the active tab on render. If you do not set an {@link #activeTab}, no tabs will be
130819  * active by default.
130820  *
130821  *     @example
130822  *     Ext.create('Ext.tab.Panel', {
130823  *         width: 300,
130824  *         height: 200,
130825  *         activeTab: 0,
130826  *         items: [
130827  *             {
130828  *                 title: 'Tab 1',
130829  *                 bodyPadding: 10,
130830  *                 html : 'A simple tab'
130831  *             },
130832  *             {
130833  *                 title: 'Tab 2',
130834  *                 html : 'Another one'
130835  *             }
130836  *         ],
130837  *         renderTo : Ext.getBody()
130838  *     });
130839  *
130840  * It is easy to control the visibility of items in the tab bar. Specify hidden: true to have the
130841  * tab button hidden initially. Items can be subsequently hidden and show by accessing the
130842  * tab property on the child item.
130843  *
130844  *     @example
130845  *     var tabs = Ext.create('Ext.tab.Panel', {
130846  *         width: 400,
130847  *         height: 400,
130848  *         renderTo: document.body,
130849  *         items: [{
130850  *             title: 'Home',
130851  *             html: 'Home',
130852  *             itemId: 'home'
130853  *         }, {
130854  *             title: 'Users',
130855  *             html: 'Users',
130856  *             itemId: 'users',
130857  *             hidden: true
130858  *         }, {
130859  *             title: 'Tickets',
130860  *             html: 'Tickets',
130861  *             itemId: 'tickets'
130862  *         }]
130863  *     });
130864  *
130865  *     setTimeout(function(){
130866  *         tabs.child('#home').tab.hide();
130867  *         var users = tabs.child('#users');
130868  *         users.tab.show();
130869  *         tabs.setActiveTab(users);
130870  *     }, 1000);
130871  *
130872  * You can remove the background of the TabBar by setting the {@link #plain} property to `true`.
130873  *
130874  *     @example
130875  *     Ext.create('Ext.tab.Panel', {
130876  *         width: 300,
130877  *         height: 200,
130878  *         activeTab: 0,
130879  *         plain: true,
130880  *         items: [
130881  *             {
130882  *                 title: 'Tab 1',
130883  *                 bodyPadding: 10,
130884  *                 html : 'A simple tab'
130885  *             },
130886  *             {
130887  *                 title: 'Tab 2',
130888  *                 html : 'Another one'
130889  *             }
130890  *         ],
130891  *         renderTo : Ext.getBody()
130892  *     });
130893  *
130894  * Another useful configuration of TabPanel is {@link #tabPosition}. This allows you to change the
130895  * position where the tabs are displayed. The available options for this are `'top'` (default) and
130896  * `'bottom'`.
130897  *
130898  *     @example
130899  *     Ext.create('Ext.tab.Panel', {
130900  *         width: 300,
130901  *         height: 200,
130902  *         activeTab: 0,
130903  *         bodyPadding: 10,
130904  *         tabPosition: 'bottom',
130905  *         items: [
130906  *             {
130907  *                 title: 'Tab 1',
130908  *                 html : 'A simple tab'
130909  *             },
130910  *             {
130911  *                 title: 'Tab 2',
130912  *                 html : 'Another one'
130913  *             }
130914  *         ],
130915  *         renderTo : Ext.getBody()
130916  *     });
130917  *
130918  * The {@link #setActiveTab} is a very useful method in TabPanel which will allow you to change the
130919  * current active tab. You can either give it an index or an instance of a tab. For example:
130920  *
130921  *     @example
130922  *     var tabs = Ext.create('Ext.tab.Panel', {
130923  *         items: [
130924  *             {
130925  *                 id   : 'my-tab',
130926  *                 title: 'Tab 1',
130927  *                 html : 'A simple tab'
130928  *             },
130929  *             {
130930  *                 title: 'Tab 2',
130931  *                 html : 'Another one'
130932  *             }
130933  *         ],
130934  *         renderTo : Ext.getBody()
130935  *     });
130936  *
130937  *     var tab = Ext.getCmp('my-tab');
130938  *
130939  *     Ext.create('Ext.button.Button', {
130940  *         renderTo: Ext.getBody(),
130941  *         text    : 'Select the first tab',
130942  *         scope   : this,
130943  *         handler : function() {
130944  *             tabs.setActiveTab(tab);
130945  *         }
130946  *     });
130947  *
130948  *     Ext.create('Ext.button.Button', {
130949  *         text    : 'Select the second tab',
130950  *         scope   : this,
130951  *         handler : function() {
130952  *             tabs.setActiveTab(1);
130953  *         },
130954  *         renderTo : Ext.getBody()
130955  *     });
130956  *
130957  * The {@link #getActiveTab} is a another useful method in TabPanel which will return the current active tab.
130958  *
130959  *     @example
130960  *     var tabs = Ext.create('Ext.tab.Panel', {
130961  *         items: [
130962  *             {
130963  *                 title: 'Tab 1',
130964  *                 html : 'A simple tab'
130965  *             },
130966  *             {
130967  *                 title: 'Tab 2',
130968  *                 html : 'Another one'
130969  *             }
130970  *         ],
130971  *         renderTo : Ext.getBody()
130972  *     });
130973  *
130974  *     Ext.create('Ext.button.Button', {
130975  *         text    : 'Get active tab',
130976  *         scope   : this,
130977  *         handler : function() {
130978  *             var tab = tabs.getActiveTab();
130979  *             alert('Current tab: ' + tab.title);
130980  *         },
130981  *         renderTo : Ext.getBody()
130982  *     });
130983  *
130984  * Adding a new tab is very simple with a TabPanel. You simple call the {@link #add} method with an config
130985  * object for a panel.
130986  *
130987  *     @example
130988  *     var tabs = Ext.create('Ext.tab.Panel', {
130989  *         items: [
130990  *             {
130991  *                 title: 'Tab 1',
130992  *                 html : 'A simple tab'
130993  *             },
130994  *             {
130995  *                 title: 'Tab 2',
130996  *                 html : 'Another one'
130997  *             }
130998  *         ],
130999  *         renderTo : Ext.getBody()
131000  *     });
131001  *
131002  *     Ext.create('Ext.button.Button', {
131003  *         text    : 'New tab',
131004  *         scope   : this,
131005  *         handler : function() {
131006  *             var tab = tabs.add({
131007  *                 // we use the tabs.items property to get the length of current items/tabs
131008  *                 title: 'Tab ' + (tabs.items.length + 1),
131009  *                 html : 'Another one'
131010  *             });
131011  *
131012  *             tabs.setActiveTab(tab);
131013  *         },
131014  *         renderTo : Ext.getBody()
131015  *     });
131016  *
131017  * Additionally, removing a tab is very also simple with a TabPanel. You simple call the {@link #remove} method
131018  * with an config object for a panel.
131019  *
131020  *     @example
131021  *     var tabs = Ext.create('Ext.tab.Panel', {
131022  *         items: [
131023  *             {
131024  *                 title: 'Tab 1',
131025  *                 html : 'A simple tab'
131026  *             },
131027  *             {
131028  *                 id   : 'remove-this-tab',
131029  *                 title: 'Tab 2',
131030  *                 html : 'Another one'
131031  *             }
131032  *         ],
131033  *         renderTo : Ext.getBody()
131034  *     });
131035  *
131036  *     Ext.create('Ext.button.Button', {
131037  *         text    : 'Remove tab',
131038  *         scope   : this,
131039  *         handler : function() {
131040  *             var tab = Ext.getCmp('remove-this-tab');
131041  *             tabs.remove(tab);
131042  *         },
131043  *         renderTo : Ext.getBody()
131044  *     });
131045  */
131046 Ext.define('Ext.tab.Panel', {
131047     extend: 'Ext.panel.Panel',
131048     alias: 'widget.tabpanel',
131049     alternateClassName: ['Ext.TabPanel'],
131050
131051     requires: ['Ext.layout.container.Card', 'Ext.tab.Bar'],
131052
131053     /**
131054      * @cfg {String} tabPosition
131055      * The position where the tab strip should be rendered. Can be `top` or `bottom`.
131056      */
131057     tabPosition : 'top',
131058
131059     /**
131060      * @cfg {String/Number} activeItem
131061      * Doesn't apply for {@link Ext.tab.Panel TabPanel}, use {@link #activeTab} instead.
131062      */
131063
131064     /**
131065      * @cfg {String/Number/Ext.Component} activeTab
131066      * The tab to activate initially. Either an ID, index or the tab component itself.
131067      */
131068
131069     /**
131070      * @cfg {Object} tabBar
131071      * Optional configuration object for the internal {@link Ext.tab.Bar}.
131072      * If present, this is passed straight through to the TabBar's constructor
131073      */
131074
131075     /**
131076      * @cfg {Object} layout
131077      * Optional configuration object for the internal {@link Ext.layout.container.Card card layout}.
131078      * If present, this is passed straight through to the layout's constructor
131079      */
131080
131081     /**
131082      * @cfg {Boolean} removePanelHeader
131083      * True to instruct each Panel added to the TabContainer to not render its header element.
131084      * This is to ensure that the title of the panel does not appear twice.
131085      */
131086     removePanelHeader: true,
131087
131088     /**
131089      * @cfg {Boolean} plain
131090      * True to not show the full background on the TabBar.
131091      */
131092     plain: false,
131093
131094     /**
131095      * @cfg {String} itemCls
131096      * The class added to each child item of this TabPanel.
131097      */
131098     itemCls: 'x-tabpanel-child',
131099
131100     /**
131101      * @cfg {Number} minTabWidth
131102      * The minimum width for a tab in the {@link #tabBar}.
131103      */
131104     minTabWidth: undefined,
131105
131106     /**
131107      * @cfg {Number} maxTabWidth The maximum width for each tab.
131108      */
131109     maxTabWidth: undefined,
131110
131111     /**
131112      * @cfg {Boolean} deferredRender
131113      *
131114      * True by default to defer the rendering of child {@link Ext.container.Container#items items} to the browsers DOM
131115      * until a tab is activated. False will render all contained {@link Ext.container.Container#items items} as soon as
131116      * the {@link Ext.layout.container.Card layout} is rendered. If there is a significant amount of content or a lot of
131117      * heavy controls being rendered into panels that are not displayed by default, setting this to true might improve
131118      * performance.
131119      *
131120      * The deferredRender property is internally passed to the layout manager for TabPanels ({@link
131121      * Ext.layout.container.Card}) as its {@link Ext.layout.container.Card#deferredRender} configuration value.
131122      *
131123      * **Note**: leaving deferredRender as true means that the content within an unactivated tab will not be available
131124      */
131125     deferredRender : true,
131126
131127     //inherit docs
131128     initComponent: function() {
131129         var me = this,
131130             dockedItems = me.dockedItems || [],
131131             activeTab = me.activeTab || 0;
131132
131133         me.layout = Ext.create('Ext.layout.container.Card', Ext.apply({
131134             owner: me,
131135             deferredRender: me.deferredRender,
131136             itemCls: me.itemCls
131137         }, me.layout));
131138
131139         /**
131140          * @property {Ext.tab.Bar} tabBar Internal reference to the docked TabBar
131141          */
131142         me.tabBar = Ext.create('Ext.tab.Bar', Ext.apply({}, me.tabBar, {
131143             dock: me.tabPosition,
131144             plain: me.plain,
131145             border: me.border,
131146             cardLayout: me.layout,
131147             tabPanel: me
131148         }));
131149
131150         if (dockedItems && !Ext.isArray(dockedItems)) {
131151             dockedItems = [dockedItems];
131152         }
131153
131154         dockedItems.push(me.tabBar);
131155         me.dockedItems = dockedItems;
131156
131157         me.addEvents(
131158             /**
131159              * @event
131160              * Fires before a tab change (activated by {@link #setActiveTab}). Return false in any listener to cancel
131161              * the tabchange
131162              * @param {Ext.tab.Panel} tabPanel The TabPanel
131163              * @param {Ext.Component} newCard The card that is about to be activated
131164              * @param {Ext.Component} oldCard The card that is currently active
131165              */
131166             'beforetabchange',
131167
131168             /**
131169              * @event
131170              * Fires when a new tab has been activated (activated by {@link #setActiveTab}).
131171              * @param {Ext.tab.Panel} tabPanel The TabPanel
131172              * @param {Ext.Component} newCard The newly activated item
131173              * @param {Ext.Component} oldCard The previously active item
131174              */
131175             'tabchange'
131176         );
131177         me.callParent(arguments);
131178
131179         //set the active tab
131180         me.setActiveTab(activeTab);
131181         //set the active tab after initial layout
131182         me.on('afterlayout', me.afterInitialLayout, me, {single: true});
131183     },
131184
131185     /**
131186      * @private
131187      * We have to wait until after the initial layout to visually activate the activeTab (if set).
131188      * The active tab has different margins than normal tabs, so if the initial layout happens with
131189      * a tab active, its layout will be offset improperly due to the active margin style. Waiting
131190      * until after the initial layout avoids this issue.
131191      */
131192     afterInitialLayout: function() {
131193         var me = this,
131194             card = me.getComponent(me.activeTab);
131195
131196         if (card) {
131197             me.layout.setActiveItem(card);
131198         }
131199     },
131200
131201     /**
131202      * Makes the given card active. Makes it the visible card in the TabPanel's CardLayout and highlights the Tab.
131203      * @param {String/Number/Ext.Component} card The card to make active. Either an ID, index or the component itself.
131204      */
131205     setActiveTab: function(card) {
131206         var me = this,
131207             previous;
131208
131209         card = me.getComponent(card);
131210         if (card) {
131211             previous = me.getActiveTab();
131212
131213             if (previous && previous !== card && me.fireEvent('beforetabchange', me, card, previous) === false) {
131214                 return false;
131215             }
131216
131217             me.tabBar.setActiveTab(card.tab);
131218             me.activeTab = card;
131219             if (me.rendered) {
131220                 me.layout.setActiveItem(card);
131221             }
131222
131223             if (previous && previous !== card) {
131224                 me.fireEvent('tabchange', me, card, previous);
131225             }
131226         }
131227     },
131228
131229     /**
131230      * Returns the item that is currently active inside this TabPanel. Note that before the TabPanel first activates a
131231      * child component this will return whatever was configured in the {@link #activeTab} config option
131232      * @return {String/Number/Ext.Component} The currently active item
131233      */
131234     getActiveTab: function() {
131235         return this.activeTab;
131236     },
131237
131238     /**
131239      * Returns the {@link Ext.tab.Bar} currently used in this TabPanel
131240      * @return {Ext.tab.Bar} The TabBar
131241      */
131242     getTabBar: function() {
131243         return this.tabBar;
131244     },
131245
131246     /**
131247      * @ignore
131248      * Makes sure we have a Tab for each item added to the TabPanel
131249      */
131250     onAdd: function(item, index) {
131251         var me = this,
131252             cfg = item.tabConfig || {},
131253             defaultConfig = {
131254                 xtype: 'tab',
131255                 card: item,
131256                 disabled: item.disabled,
131257                 closable: item.closable,
131258                 hidden: item.hidden,
131259                 tabBar: me.tabBar
131260             };
131261
131262         if (item.closeText) {
131263             defaultConfig.closeText = item.closeText;
131264         }
131265         cfg = Ext.applyIf(cfg, defaultConfig);
131266         item.tab = me.tabBar.insert(index, cfg);
131267
131268         item.on({
131269             scope : me,
131270             enable: me.onItemEnable,
131271             disable: me.onItemDisable,
131272             beforeshow: me.onItemBeforeShow,
131273             iconchange: me.onItemIconChange,
131274             titlechange: me.onItemTitleChange
131275         });
131276
131277         if (item.isPanel) {
131278             if (me.removePanelHeader) {
131279                 item.preventHeader = true;
131280                 if (item.rendered) {
131281                     item.updateHeader();
131282                 }
131283             }
131284             if (item.isPanel && me.border) {
131285                 item.setBorder(false);
131286             }
131287         }
131288
131289         // ensure that there is at least one active tab
131290         if (this.rendered && me.items.getCount() === 1) {
131291             me.setActiveTab(0);
131292         }
131293     },
131294
131295     /**
131296      * @private
131297      * Enable corresponding tab when item is enabled.
131298      */
131299     onItemEnable: function(item){
131300         item.tab.enable();
131301     },
131302
131303     /**
131304      * @private
131305      * Disable corresponding tab when item is enabled.
131306      */
131307     onItemDisable: function(item){
131308         item.tab.disable();
131309     },
131310
131311     /**
131312      * @private
131313      * Sets activeTab before item is shown.
131314      */
131315     onItemBeforeShow: function(item) {
131316         if (item !== this.activeTab) {
131317             this.setActiveTab(item);
131318             return false;
131319         }
131320     },
131321
131322     /**
131323      * @private
131324      * Update the tab iconCls when panel iconCls has been set or changed.
131325      */
131326     onItemIconChange: function(item, newIconCls) {
131327         item.tab.setIconCls(newIconCls);
131328         this.getTabBar().doLayout();
131329     },
131330
131331     /**
131332      * @private
131333      * Update the tab title when panel title has been set or changed.
131334      */
131335     onItemTitleChange: function(item, newTitle) {
131336         item.tab.setText(newTitle);
131337         this.getTabBar().doLayout();
131338     },
131339
131340
131341     /**
131342      * @ignore
131343      * If we're removing the currently active tab, activate the nearest one. The item is removed when we call super,
131344      * so we can do preprocessing before then to find the card's index
131345      */
131346     doRemove: function(item, autoDestroy) {
131347         var me = this,
131348             items = me.items,
131349             // At this point the item hasn't been removed from the items collection.
131350             // As such, if we want to check if there are no more tabs left, we have to
131351             // check for one, as opposed to 0.
131352             hasItemsLeft = items.getCount() > 1;
131353
131354         if (me.destroying || !hasItemsLeft) {
131355             me.activeTab = null;
131356         } else if (item === me.activeTab) {
131357              me.setActiveTab(item.next() || items.getAt(0));
131358         }
131359         me.callParent(arguments);
131360
131361         // Remove the two references
131362         delete item.tab.card;
131363         delete item.tab;
131364     },
131365
131366     /**
131367      * @ignore
131368      * Makes sure we remove the corresponding Tab when an item is removed
131369      */
131370     onRemove: function(item, autoDestroy) {
131371         var me = this;
131372
131373         item.un({
131374             scope : me,
131375             enable: me.onItemEnable,
131376             disable: me.onItemDisable,
131377             beforeshow: me.onItemBeforeShow
131378         });
131379         if (!me.destroying && item.tab.ownerCt == me.tabBar) {
131380             me.tabBar.remove(item.tab);
131381         }
131382     }
131383 });
131384
131385 /**
131386  * A simple element that adds extra horizontal space between items in a toolbar.
131387  * By default a 2px wide space is added via CSS specification:
131388  *
131389  *     .x-toolbar .x-toolbar-spacer {
131390  *         width: 2px;
131391  *     }
131392  *
131393  * Example:
131394  *
131395  *     @example
131396  *     Ext.create('Ext.panel.Panel', {
131397  *         title: 'Toolbar Spacer Example',
131398  *         width: 300,
131399  *         height: 200,
131400  *         tbar : [
131401  *             'Item 1',
131402  *             { xtype: 'tbspacer' }, // or ' '
131403  *             'Item 2',
131404  *             // space width is also configurable via javascript
131405  *             { xtype: 'tbspacer', width: 50 }, // add a 50px space
131406  *             'Item 3'
131407  *         ],
131408  *         renderTo: Ext.getBody()
131409  *     });
131410  */
131411 Ext.define('Ext.toolbar.Spacer', {
131412     extend: 'Ext.Component',
131413     alias: 'widget.tbspacer',
131414     alternateClassName: 'Ext.Toolbar.Spacer',
131415     baseCls: Ext.baseCSSPrefix + 'toolbar-spacer',
131416     focusable: false
131417 });
131418 /**
131419  * @class Ext.tree.Column
131420  * @extends Ext.grid.column.Column
131421  * 
131422  * Provides indentation and folder structure markup for a Tree taking into account
131423  * depth and position within the tree hierarchy.
131424  * 
131425  * @private
131426  */
131427 Ext.define('Ext.tree.Column', {
131428     extend: 'Ext.grid.column.Column',
131429     alias: 'widget.treecolumn',
131430
131431     initComponent: function() {
131432         var origRenderer = this.renderer || this.defaultRenderer,
131433             origScope    = this.scope || window;
131434
131435         this.renderer = function(value, metaData, record, rowIdx, colIdx, store, view) {
131436             var buf   = [],
131437                 format = Ext.String.format,
131438                 depth = record.getDepth(),
131439                 treePrefix  = Ext.baseCSSPrefix + 'tree-',
131440                 elbowPrefix = treePrefix + 'elbow-',
131441                 expanderCls = treePrefix + 'expander',
131442                 imgText     = '<img src="{1}" class="{0}" />',
131443                 checkboxText= '<input type="button" role="checkbox" class="{0}" {1} />',
131444                 formattedValue = origRenderer.apply(origScope, arguments),
131445                 href = record.get('href'),
131446                 target = record.get('hrefTarget'),
131447                 cls = record.get('cls');
131448
131449             while (record) {
131450                 if (!record.isRoot() || (record.isRoot() && view.rootVisible)) {
131451                     if (record.getDepth() === depth) {
131452                         buf.unshift(format(imgText,
131453                             treePrefix + 'icon ' + 
131454                             treePrefix + 'icon' + (record.get('icon') ? '-inline ' : (record.isLeaf() ? '-leaf ' : '-parent ')) +
131455                             (record.get('iconCls') || ''),
131456                             record.get('icon') || Ext.BLANK_IMAGE_URL
131457                         ));
131458                         if (record.get('checked') !== null) {
131459                             buf.unshift(format(
131460                                 checkboxText,
131461                                 (treePrefix + 'checkbox') + (record.get('checked') ? ' ' + treePrefix + 'checkbox-checked' : ''),
131462                                 record.get('checked') ? 'aria-checked="true"' : ''
131463                             ));
131464                             if (record.get('checked')) {
131465                                 metaData.tdCls += (' ' + treePrefix + 'checked');
131466                             }
131467                         }
131468                         if (record.isLast()) {
131469                             if (record.isExpandable()) {
131470                                 buf.unshift(format(imgText, (elbowPrefix + 'end-plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
131471                             } else {
131472                                 buf.unshift(format(imgText, (elbowPrefix + 'end'), Ext.BLANK_IMAGE_URL));
131473                             }
131474                             
131475                         } else {
131476                             if (record.isExpandable()) {
131477                                 buf.unshift(format(imgText, (elbowPrefix + 'plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
131478                             } else {
131479                                 buf.unshift(format(imgText, (treePrefix + 'elbow'), Ext.BLANK_IMAGE_URL));
131480                             }
131481                         }
131482                     } else {
131483                         if (record.isLast() || record.getDepth() === 0) {
131484                             buf.unshift(format(imgText, (elbowPrefix + 'empty'), Ext.BLANK_IMAGE_URL));
131485                         } else if (record.getDepth() !== 0) {
131486                             buf.unshift(format(imgText, (elbowPrefix + 'line'), Ext.BLANK_IMAGE_URL));
131487                         }                      
131488                     }
131489                 }
131490                 record = record.parentNode;
131491             }
131492             if (href) {
131493                 buf.push('<a href="', href, '" target="', target, '">', formattedValue, '</a>');
131494             } else {
131495                 buf.push(formattedValue);
131496             }
131497             if (cls) {
131498                 metaData.tdCls += ' ' + cls;
131499             }
131500             return buf.join('');
131501         };
131502         this.callParent(arguments);
131503     },
131504
131505     defaultRenderer: function(value) {
131506         return value;
131507     }
131508 });
131509 /**
131510  * Used as a view by {@link Ext.tree.Panel TreePanel}.
131511  */
131512 Ext.define('Ext.tree.View', {
131513     extend: 'Ext.view.Table',
131514     alias: 'widget.treeview',
131515
131516     loadingCls: Ext.baseCSSPrefix + 'grid-tree-loading',
131517     expandedCls: Ext.baseCSSPrefix + 'grid-tree-node-expanded',
131518
131519     expanderSelector: '.' + Ext.baseCSSPrefix + 'tree-expander',
131520     checkboxSelector: '.' + Ext.baseCSSPrefix + 'tree-checkbox',
131521     expanderIconOverCls: Ext.baseCSSPrefix + 'tree-expander-over',
131522
131523     // Class to add to the node wrap element used to hold nodes when a parent is being
131524     // collapsed or expanded. During the animation, UI interaction is forbidden by testing
131525     // for an ancestor node with this class.
131526     nodeAnimWrapCls: Ext.baseCSSPrefix + 'tree-animator-wrap',
131527
131528     blockRefresh: true,
131529
131530     /** 
131531      * @cfg {Boolean} rootVisible
131532      * False to hide the root node.
131533      */
131534     rootVisible: true,
131535
131536     /** 
131537      * @cfg {Boolean} animate
131538      * True to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx Ext.enableFx})
131539      */
131540
131541     expandDuration: 250,
131542     collapseDuration: 250,
131543     
131544     toggleOnDblClick: true,
131545
131546     initComponent: function() {
131547         var me = this;
131548         
131549         if (me.initialConfig.animate === undefined) {
131550             me.animate = Ext.enableFx;
131551         }
131552         
131553         me.store = Ext.create('Ext.data.NodeStore', {
131554             recursive: true,
131555             rootVisible: me.rootVisible,
131556             listeners: {
131557                 beforeexpand: me.onBeforeExpand,
131558                 expand: me.onExpand,
131559                 beforecollapse: me.onBeforeCollapse,
131560                 collapse: me.onCollapse,
131561                 scope: me
131562             }
131563         });
131564         
131565         if (me.node) {
131566             me.setRootNode(me.node);
131567         }
131568         me.animQueue = {};
131569         me.callParent(arguments);
131570     },
131571
131572     processUIEvent: function(e) {
131573         // If the clicked node is part of an animation, ignore the click.
131574         // This is because during a collapse animation, the associated Records
131575         // will already have been removed from the Store, and the event is not processable.
131576         if (e.getTarget('.' + this.nodeAnimWrapCls, this.el)) {
131577             return false;
131578         }
131579         return this.callParent(arguments);
131580     },
131581
131582     onClear: function(){
131583         this.store.removeAll();    
131584     },
131585
131586     setRootNode: function(node) {
131587         var me = this;        
131588         me.store.setNode(node);
131589         me.node = node;
131590         if (!me.rootVisible) {
131591             node.expand();
131592         }
131593     },
131594     
131595     onRender: function() {
131596         var me = this,
131597             el;
131598
131599         me.callParent(arguments);
131600
131601         el = me.el;
131602         el.on({
131603             scope: me,
131604             delegate: me.expanderSelector,
131605             mouseover: me.onExpanderMouseOver,
131606             mouseout: me.onExpanderMouseOut
131607         });
131608         el.on({
131609             scope: me,
131610             delegate: me.checkboxSelector,
131611             click: me.onCheckboxChange
131612         });
131613     },
131614
131615     onCheckboxChange: function(e, t) {
131616         var me = this,
131617             item = e.getTarget(me.getItemSelector(), me.getTargetEl());
131618             
131619         if (item) {
131620             me.onCheckChange(me.getRecord(item));
131621         }
131622     },
131623     
131624     onCheckChange: function(record){
131625         var checked = record.get('checked');
131626         if (Ext.isBoolean(checked)) {
131627             checked = !checked;
131628             record.set('checked', checked);
131629             this.fireEvent('checkchange', record, checked);
131630         }
131631     },
131632
131633     getChecked: function() {
131634         var checked = [];
131635         this.node.cascadeBy(function(rec){
131636             if (rec.get('checked')) {
131637                 checked.push(rec);
131638             }
131639         });
131640         return checked;
131641     },
131642     
131643     isItemChecked: function(rec){
131644         return rec.get('checked');
131645     },
131646
131647     createAnimWrap: function(record, index) {
131648         var thHtml = '',
131649             headerCt = this.panel.headerCt,
131650             headers = headerCt.getGridColumns(),
131651             i = 0, len = headers.length, item,
131652             node = this.getNode(record),
131653             tmpEl, nodeEl;
131654
131655         for (; i < len; i++) {
131656             item = headers[i];
131657             thHtml += '<th style="width: ' + (item.hidden ? 0 : item.getDesiredWidth()) + 'px; height: 0px;"></th>';
131658         }
131659
131660         nodeEl = Ext.get(node);        
131661         tmpEl = nodeEl.insertSibling({
131662             tag: 'tr',
131663             html: [
131664                 '<td colspan="' + headerCt.getColumnCount() + '">',
131665                     '<div class="' + this.nodeAnimWrapCls + '">',
131666                         '<table class="' + Ext.baseCSSPrefix + 'grid-table" style="width: ' + headerCt.getFullWidth() + 'px;"><tbody>',
131667                             thHtml,
131668                         '</tbody></table>',
131669                     '</div>',
131670                 '</td>'
131671             ].join('')
131672         }, 'after');
131673
131674         return {
131675             record: record,
131676             node: node,
131677             el: tmpEl,
131678             expanding: false,
131679             collapsing: false,
131680             animating: false,
131681             animateEl: tmpEl.down('div'),
131682             targetEl: tmpEl.down('tbody')
131683         };
131684     },
131685
131686     getAnimWrap: function(parent) {
131687         if (!this.animate) {
131688             return null;
131689         }
131690
131691         // We are checking to see which parent is having the animation wrap
131692         while (parent) {
131693             if (parent.animWrap) {
131694                 return parent.animWrap;
131695             }
131696             parent = parent.parentNode;
131697         }
131698         return null;
131699     },
131700
131701     doAdd: function(nodes, records, index) {
131702         // If we are adding records which have a parent that is currently expanding
131703         // lets add them to the animation wrap
131704         var me = this,
131705             record = records[0],
131706             parent = record.parentNode,
131707             a = me.all.elements,
131708             relativeIndex = 0,
131709             animWrap = me.getAnimWrap(parent),
131710             targetEl, children, len;
131711
131712         if (!animWrap || !animWrap.expanding) {
131713             me.resetScrollers();
131714             return me.callParent(arguments);
131715         }
131716
131717         // We need the parent that has the animWrap, not the nodes parent
131718         parent = animWrap.record;
131719         
131720         // If there is an anim wrap we do our special magic logic
131721         targetEl = animWrap.targetEl;
131722         children = targetEl.dom.childNodes;
131723         
131724         // We subtract 1 from the childrens length because we have a tr in there with the th'es
131725         len = children.length - 1;
131726         
131727         // The relative index is the index in the full flat collection minus the index of the wraps parent
131728         relativeIndex = index - me.indexOf(parent) - 1;
131729         
131730         // If we are adding records to the wrap that have a higher relative index then there are currently children
131731         // it means we have to append the nodes to the wrap
131732         if (!len || relativeIndex >= len) {
131733             targetEl.appendChild(nodes);
131734         }
131735         // If there are already more children then the relative index it means we are adding child nodes of
131736         // some expanded node in the anim wrap. In this case we have to insert the nodes in the right location
131737         else {
131738             // +1 because of the tr with th'es that is already there
131739             Ext.fly(children[relativeIndex + 1]).insertSibling(nodes, 'before', true);
131740         }
131741
131742         // We also have to update the CompositeElementLite collection of the DataView
131743         Ext.Array.insert(a, index, nodes);
131744         
131745         // If we were in an animation we need to now change the animation
131746         // because the targetEl just got higher.
131747         if (animWrap.isAnimating) {
131748             me.onExpand(parent);
131749         }
131750     },
131751     
131752     beginBulkUpdate: function(){
131753         this.bulkUpdate = true;
131754         this.ownerCt.changingScrollbars = true;  
131755     },
131756     
131757     endBulkUpdate: function(){
131758         var me = this,
131759             ownerCt = me.ownerCt;
131760         
131761         me.bulkUpdate = false;
131762         me.ownerCt.changingScrollbars = true;  
131763         me.resetScrollers();  
131764     },
131765     
131766     onRemove : function(ds, record, index) {
131767         var me = this,
131768             bulk = me.bulkUpdate;
131769
131770         me.doRemove(record, index);
131771         if (!bulk) {
131772             me.updateIndexes(index);
131773         }
131774         if (me.store.getCount() === 0){
131775             me.refresh();
131776         }
131777         if (!bulk) {
131778             me.fireEvent('itemremove', record, index);
131779         }
131780     },
131781     
131782     doRemove: function(record, index) {
131783         // If we are adding records which have a parent that is currently expanding
131784         // lets add them to the animation wrap
131785         var me = this,
131786             parent = record.parentNode,
131787             all = me.all,
131788             animWrap = me.getAnimWrap(record),
131789             node = all.item(index).dom;
131790
131791         if (!animWrap || !animWrap.collapsing) {
131792             me.resetScrollers();
131793             return me.callParent(arguments);
131794         }
131795
131796         animWrap.targetEl.appendChild(node);
131797         all.removeElement(index);
131798     },
131799
131800     onBeforeExpand: function(parent, records, index) {
131801         var me = this,
131802             animWrap;
131803             
131804         if (!me.rendered || !me.animate) {
131805             return;
131806         }
131807
131808         if (me.getNode(parent)) {
131809             animWrap = me.getAnimWrap(parent);
131810             if (!animWrap) {
131811                 animWrap = parent.animWrap = me.createAnimWrap(parent);
131812                 animWrap.animateEl.setHeight(0);
131813             }
131814             else if (animWrap.collapsing) {
131815                 // If we expand this node while it is still expanding then we
131816                 // have to remove the nodes from the animWrap.
131817                 animWrap.targetEl.select(me.itemSelector).remove();
131818             } 
131819             animWrap.expanding = true;
131820             animWrap.collapsing = false;
131821         }
131822     },
131823
131824     onExpand: function(parent) {
131825         var me = this,
131826             queue = me.animQueue,
131827             id = parent.getId(),
131828             animWrap,
131829             animateEl, 
131830             targetEl,
131831             queueItem;        
131832         
131833         if (me.singleExpand) {
131834             me.ensureSingleExpand(parent);
131835         }
131836         
131837         animWrap = me.getAnimWrap(parent);
131838
131839         if (!animWrap) {
131840             me.resetScrollers();
131841             return;
131842         }
131843         
131844         animateEl = animWrap.animateEl;
131845         targetEl = animWrap.targetEl;
131846
131847         animateEl.stopAnimation();
131848         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
131849         queue[id] = true;
131850         animateEl.slideIn('t', {
131851             duration: me.expandDuration,
131852             listeners: {
131853                 scope: me,
131854                 lastframe: function() {
131855                     // Move all the nodes out of the anim wrap to their proper location
131856                     animWrap.el.insertSibling(targetEl.query(me.itemSelector), 'before');
131857                     animWrap.el.remove();
131858                     me.resetScrollers();
131859                     delete animWrap.record.animWrap;
131860                     delete queue[id];
131861                 }
131862             }
131863         });
131864         
131865         animWrap.isAnimating = true;
131866     },
131867     
131868     resetScrollers: function(){
131869         if (!this.bulkUpdate) {
131870             var panel = this.panel;
131871             
131872             panel.determineScrollbars();
131873             panel.invalidateScroller();
131874         }
131875     },
131876
131877     onBeforeCollapse: function(parent, records, index) {
131878         var me = this,
131879             animWrap;
131880             
131881         if (!me.rendered || !me.animate) {
131882             return;
131883         }
131884
131885         if (me.getNode(parent)) {
131886             animWrap = me.getAnimWrap(parent);
131887             if (!animWrap) {
131888                 animWrap = parent.animWrap = me.createAnimWrap(parent, index);
131889             }
131890             else if (animWrap.expanding) {
131891                 // If we collapse this node while it is still expanding then we
131892                 // have to remove the nodes from the animWrap.
131893                 animWrap.targetEl.select(this.itemSelector).remove();
131894             }
131895             animWrap.expanding = false;
131896             animWrap.collapsing = true;
131897         }
131898     },
131899     
131900     onCollapse: function(parent) {
131901         var me = this,
131902             queue = me.animQueue,
131903             id = parent.getId(),
131904             animWrap = me.getAnimWrap(parent),
131905             animateEl, targetEl;
131906
131907         if (!animWrap) {
131908             me.resetScrollers();
131909             return;
131910         }
131911         
131912         animateEl = animWrap.animateEl;
131913         targetEl = animWrap.targetEl;
131914
131915         queue[id] = true;
131916         
131917         // @TODO: we are setting it to 1 because quirks mode on IE seems to have issues with 0
131918         animateEl.stopAnimation();
131919         animateEl.slideOut('t', {
131920             duration: me.collapseDuration,
131921             listeners: {
131922                 scope: me,
131923                 lastframe: function() {
131924                     animWrap.el.remove();
131925                     delete animWrap.record.animWrap;
131926                     me.resetScrollers();
131927                     delete queue[id];
131928                 }             
131929             }
131930         });
131931         animWrap.isAnimating = true;
131932     },
131933     
131934     /**
131935      * Checks if a node is currently undergoing animation
131936      * @private
131937      * @param {Ext.data.Model} node The node
131938      * @return {Boolean} True if the node is animating
131939      */
131940     isAnimating: function(node) {
131941         return !!this.animQueue[node.getId()];    
131942     },
131943     
131944     collectData: function(records) {
131945         var data = this.callParent(arguments),
131946             rows = data.rows,
131947             len = rows.length,
131948             i = 0,
131949             row, record;
131950             
131951         for (; i < len; i++) {
131952             row = rows[i];
131953             record = records[i];
131954             if (record.get('qtip')) {
131955                 row.rowAttr = 'data-qtip="' + record.get('qtip') + '"';
131956                 if (record.get('qtitle')) {
131957                     row.rowAttr += ' ' + 'data-qtitle="' + record.get('qtitle') + '"';
131958                 }
131959             }
131960             if (record.isExpanded()) {
131961                 row.rowCls = (row.rowCls || '') + ' ' + this.expandedCls;
131962             }
131963             if (record.isLoading()) {
131964                 row.rowCls = (row.rowCls || '') + ' ' + this.loadingCls;
131965             }
131966         }
131967         
131968         return data;
131969     },
131970     
131971     /**
131972      * Expands a record that is loaded in the view.
131973      * @param {Ext.data.Model} record The record to expand
131974      * @param {Boolean} deep (optional) True to expand nodes all the way down the tree hierarchy.
131975      * @param {Function} callback (optional) The function to run after the expand is completed
131976      * @param {Object} scope (optional) The scope of the callback function.
131977      */
131978     expand: function(record, deep, callback, scope) {
131979         return record.expand(deep, callback, scope);
131980     },
131981     
131982     /**
131983      * Collapses a record that is loaded in the view.
131984      * @param {Ext.data.Model} record The record to collapse
131985      * @param {Boolean} deep (optional) True to collapse nodes all the way up the tree hierarchy.
131986      * @param {Function} callback (optional) The function to run after the collapse is completed
131987      * @param {Object} scope (optional) The scope of the callback function.
131988      */
131989     collapse: function(record, deep, callback, scope) {
131990         return record.collapse(deep, callback, scope);
131991     },
131992     
131993     /**
131994      * Toggles a record between expanded and collapsed.
131995      * @param {Ext.data.Model} recordInstance
131996      */
131997     toggle: function(record) {
131998         this[record.isExpanded() ? 'collapse' : 'expand'](record);
131999     },
132000     
132001     onItemDblClick: function(record, item, index) {
132002         this.callParent(arguments);
132003         if (this.toggleOnDblClick) {
132004             this.toggle(record);
132005         }
132006     },
132007     
132008     onBeforeItemMouseDown: function(record, item, index, e) {
132009         if (e.getTarget(this.expanderSelector, item)) {
132010             return false;
132011         }
132012         return this.callParent(arguments);
132013     },
132014     
132015     onItemClick: function(record, item, index, e) {
132016         if (e.getTarget(this.expanderSelector, item)) {
132017             this.toggle(record);
132018             return false;
132019         }
132020         return this.callParent(arguments);
132021     },
132022     
132023     onExpanderMouseOver: function(e, t) {
132024         e.getTarget(this.cellSelector, 10, true).addCls(this.expanderIconOverCls);
132025     },
132026     
132027     onExpanderMouseOut: function(e, t) {
132028         e.getTarget(this.cellSelector, 10, true).removeCls(this.expanderIconOverCls);
132029     },
132030     
132031     /**
132032      * Gets the base TreeStore from the bound TreePanel.
132033      */
132034     getTreeStore: function() {
132035         return this.panel.store;
132036     },    
132037     
132038     ensureSingleExpand: function(node) {
132039         var parent = node.parentNode;
132040         if (parent) {
132041             parent.eachChild(function(child) {
132042                 if (child !== node && child.isExpanded()) {
132043                     child.collapse();
132044                 }
132045             });
132046         }
132047     }
132048 });
132049 /**
132050  * The TreePanel provides tree-structured UI representation of tree-structured data.
132051  * A TreePanel must be bound to a {@link Ext.data.TreeStore}. TreePanel's support
132052  * multiple columns through the {@link #columns} configuration.
132053  *
132054  * Simple TreePanel using inline data:
132055  *
132056  *     @example
132057  *     var store = Ext.create('Ext.data.TreeStore', {
132058  *         root: {
132059  *             expanded: true,
132060  *             children: [
132061  *                 { text: "detention", leaf: true },
132062  *                 { text: "homework", expanded: true, children: [
132063  *                     { text: "book report", leaf: true },
132064  *                     { text: "alegrbra", leaf: true}
132065  *                 ] },
132066  *                 { text: "buy lottery tickets", leaf: true }
132067  *             ]
132068  *         }
132069  *     });
132070  *
132071  *     Ext.create('Ext.tree.Panel', {
132072  *         title: 'Simple Tree',
132073  *         width: 200,
132074  *         height: 150,
132075  *         store: store,
132076  *         rootVisible: false,
132077  *         renderTo: Ext.getBody()
132078  *     });
132079  *
132080  * For the tree node config options (like `text`, `leaf`, `expanded`), see the documentation of
132081  * {@link Ext.data.NodeInterface NodeInterface} config options.
132082  */
132083 Ext.define('Ext.tree.Panel', {
132084     extend: 'Ext.panel.Table',
132085     alias: 'widget.treepanel',
132086     alternateClassName: ['Ext.tree.TreePanel', 'Ext.TreePanel'],
132087     requires: ['Ext.tree.View', 'Ext.selection.TreeModel', 'Ext.tree.Column'],
132088     viewType: 'treeview',
132089     selType: 'treemodel',
132090
132091     treeCls: Ext.baseCSSPrefix + 'tree-panel',
132092
132093     deferRowRender: false,
132094
132095     /**
132096      * @cfg {Boolean} lines False to disable tree lines.
132097      */
132098     lines: true,
132099
132100     /**
132101      * @cfg {Boolean} useArrows True to use Vista-style arrows in the tree.
132102      */
132103     useArrows: false,
132104
132105     /**
132106      * @cfg {Boolean} singleExpand True if only 1 node per branch may be expanded.
132107      */
132108     singleExpand: false,
132109
132110     ddConfig: {
132111         enableDrag: true,
132112         enableDrop: true
132113     },
132114
132115     /**
132116      * @cfg {Boolean} animate True to enable animated expand/collapse. Defaults to the value of {@link Ext#enableFx}.
132117      */
132118
132119     /**
132120      * @cfg {Boolean} rootVisible False to hide the root node.
132121      */
132122     rootVisible: true,
132123
132124     /**
132125      * @cfg {Boolean} displayField The field inside the model that will be used as the node's text.
132126      */
132127     displayField: 'text',
132128
132129     /**
132130      * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
132131      * Allows you to not specify a store on this TreePanel. This is useful for creating a simple tree with preloaded
132132      * data without having to specify a TreeStore and Model. A store and model will be created and root will be passed
132133      * to that store. For example:
132134      *
132135      *     Ext.create('Ext.tree.Panel', {
132136      *         title: 'Simple Tree',
132137      *         root: {
132138      *             text: "Root node",
132139      *             expanded: true,
132140      *             children: [
132141      *                 { text: "Child 1", leaf: true },
132142      *                 { text: "Child 2", leaf: true }
132143      *             ]
132144      *         },
132145      *         renderTo: Ext.getBody()
132146      *     });
132147      */
132148     root: null,
132149
132150     // Required for the Lockable Mixin. These are the configurations which will be copied to the
132151     // normal and locked sub tablepanels
132152     normalCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible', 'scroll'],
132153     lockedCfgCopy: ['displayField', 'root', 'singleExpand', 'useArrows', 'lines', 'rootVisible'],
132154
132155     /**
132156      * @cfg {Boolean} hideHeaders True to hide the headers. Defaults to `undefined`.
132157      */
132158
132159     /**
132160      * @cfg {Boolean} folderSort True to automatically prepend a leaf sorter to the store. Defaults to `undefined`.
132161      */
132162
132163     constructor: function(config) {
132164         config = config || {};
132165         if (config.animate === undefined) {
132166             config.animate = Ext.enableFx;
132167         }
132168         this.enableAnimations = config.animate;
132169         delete config.animate;
132170
132171         this.callParent([config]);
132172     },
132173
132174     initComponent: function() {
132175         var me = this,
132176             cls = [me.treeCls];
132177
132178         if (me.useArrows) {
132179             cls.push(Ext.baseCSSPrefix + 'tree-arrows');
132180             me.lines = false;
132181         }
132182
132183         if (me.lines) {
132184             cls.push(Ext.baseCSSPrefix + 'tree-lines');
132185         } else if (!me.useArrows) {
132186             cls.push(Ext.baseCSSPrefix + 'tree-no-lines');
132187         }
132188
132189         if (Ext.isString(me.store)) {
132190             me.store = Ext.StoreMgr.lookup(me.store);
132191         } else if (!me.store || Ext.isObject(me.store) && !me.store.isStore) {
132192             me.store = Ext.create('Ext.data.TreeStore', Ext.apply({}, me.store || {}, {
132193                 root: me.root,
132194                 fields: me.fields,
132195                 model: me.model,
132196                 folderSort: me.folderSort
132197             }));
132198         } else if (me.root) {
132199             me.store = Ext.data.StoreManager.lookup(me.store);
132200             me.store.setRootNode(me.root);
132201             if (me.folderSort !== undefined) {
132202                 me.store.folderSort = me.folderSort;
132203                 me.store.sort();
132204             }
132205         }
132206
132207         // I'm not sure if we want to this. It might be confusing
132208         // if (me.initialConfig.rootVisible === undefined && !me.getRootNode()) {
132209         //     me.rootVisible = false;
132210         // }
132211
132212         me.viewConfig = Ext.applyIf(me.viewConfig || {}, {
132213             rootVisible: me.rootVisible,
132214             animate: me.enableAnimations,
132215             singleExpand: me.singleExpand,
132216             node: me.store.getRootNode(),
132217             hideHeaders: me.hideHeaders
132218         });
132219
132220         me.mon(me.store, {
132221             scope: me,
132222             rootchange: me.onRootChange,
132223             clear: me.onClear
132224         });
132225
132226         me.relayEvents(me.store, [
132227             /**
132228              * @event beforeload
132229              * @alias Ext.data.Store#beforeload
132230              */
132231             'beforeload',
132232
132233             /**
132234              * @event load
132235              * @alias Ext.data.Store#load
132236              */
132237             'load'
132238         ]);
132239
132240         me.store.on({
132241             /**
132242              * @event itemappend
132243              * @alias Ext.data.TreeStore#append
132244              */
132245             append: me.createRelayer('itemappend'),
132246
132247             /**
132248              * @event itemremove
132249              * @alias Ext.data.TreeStore#remove
132250              */
132251             remove: me.createRelayer('itemremove'),
132252
132253             /**
132254              * @event itemmove
132255              * @alias Ext.data.TreeStore#move
132256              */
132257             move: me.createRelayer('itemmove'),
132258
132259             /**
132260              * @event iteminsert
132261              * @alias Ext.data.TreeStore#insert
132262              */
132263             insert: me.createRelayer('iteminsert'),
132264
132265             /**
132266              * @event beforeitemappend
132267              * @alias Ext.data.TreeStore#beforeappend
132268              */
132269             beforeappend: me.createRelayer('beforeitemappend'),
132270
132271             /**
132272              * @event beforeitemremove
132273              * @alias Ext.data.TreeStore#beforeremove
132274              */
132275             beforeremove: me.createRelayer('beforeitemremove'),
132276
132277             /**
132278              * @event beforeitemmove
132279              * @alias Ext.data.TreeStore#beforemove
132280              */
132281             beforemove: me.createRelayer('beforeitemmove'),
132282
132283             /**
132284              * @event beforeiteminsert
132285              * @alias Ext.data.TreeStore#beforeinsert
132286              */
132287             beforeinsert: me.createRelayer('beforeiteminsert'),
132288
132289             /**
132290              * @event itemexpand
132291              * @alias Ext.data.TreeStore#expand
132292              */
132293             expand: me.createRelayer('itemexpand'),
132294
132295             /**
132296              * @event itemcollapse
132297              * @alias Ext.data.TreeStore#collapse
132298              */
132299             collapse: me.createRelayer('itemcollapse'),
132300
132301             /**
132302              * @event beforeitemexpand
132303              * @alias Ext.data.TreeStore#beforeexpand
132304              */
132305             beforeexpand: me.createRelayer('beforeitemexpand'),
132306
132307             /**
132308              * @event beforeitemcollapse
132309              * @alias Ext.data.TreeStore#beforecollapse
132310              */
132311             beforecollapse: me.createRelayer('beforeitemcollapse')
132312         });
132313
132314         // If the user specifies the headers collection manually then dont inject our own
132315         if (!me.columns) {
132316             if (me.initialConfig.hideHeaders === undefined) {
132317                 me.hideHeaders = true;
132318             }
132319             me.columns = [{
132320                 xtype    : 'treecolumn',
132321                 text     : 'Name',
132322                 flex     : 1,
132323                 dataIndex: me.displayField
132324             }];
132325         }
132326
132327         if (me.cls) {
132328             cls.push(me.cls);
132329         }
132330         me.cls = cls.join(' ');
132331         me.callParent();
132332
132333         me.relayEvents(me.getView(), [
132334             /**
132335              * @event checkchange
132336              * Fires when a node with a checkbox's checked property changes
132337              * @param {Ext.data.Model} node The node who's checked property was changed
132338              * @param {Boolean} checked The node's new checked state
132339              */
132340             'checkchange'
132341         ]);
132342
132343         // If the root is not visible and there is no rootnode defined, then just lets load the store
132344         if (!me.getView().rootVisible && !me.getRootNode()) {
132345             me.setRootNode({
132346                 expanded: true
132347             });
132348         }
132349     },
132350
132351     onClear: function(){
132352         this.view.onClear();
132353     },
132354
132355     /**
132356      * Sets root node of this tree.
132357      * @param {Ext.data.Model/Ext.data.NodeInterface/Object} root
132358      * @return {Ext.data.NodeInterface} The new root
132359      */
132360     setRootNode: function() {
132361         return this.store.setRootNode.apply(this.store, arguments);
132362     },
132363
132364     /**
132365      * Returns the root node for this tree.
132366      * @return {Ext.data.NodeInterface}
132367      */
132368     getRootNode: function() {
132369         return this.store.getRootNode();
132370     },
132371
132372     onRootChange: function(root) {
132373         this.view.setRootNode(root);
132374     },
132375
132376     /**
132377      * Retrieve an array of checked records.
132378      * @return {Ext.data.Model[]} An array containing the checked records
132379      */
132380     getChecked: function() {
132381         return this.getView().getChecked();
132382     },
132383
132384     isItemChecked: function(rec) {
132385         return rec.get('checked');
132386     },
132387
132388     /**
132389      * Expand all nodes
132390      * @param {Function} callback (optional) A function to execute when the expand finishes.
132391      * @param {Object} scope (optional) The scope of the callback function
132392      */
132393     expandAll : function(callback, scope) {
132394         var root = this.getRootNode(),
132395             animate = this.enableAnimations,
132396             view = this.getView();
132397         if (root) {
132398             if (!animate) {
132399                 view.beginBulkUpdate();
132400             }
132401             root.expand(true, callback, scope);
132402             if (!animate) {
132403                 view.endBulkUpdate();
132404             }
132405         }
132406     },
132407
132408     /**
132409      * Collapse all nodes
132410      * @param {Function} callback (optional) A function to execute when the collapse finishes.
132411      * @param {Object} scope (optional) The scope of the callback function
132412      */
132413     collapseAll : function(callback, scope) {
132414         var root = this.getRootNode(),
132415             animate = this.enableAnimations,
132416             view = this.getView();
132417
132418         if (root) {
132419             if (!animate) {
132420                 view.beginBulkUpdate();
132421             }
132422             if (view.rootVisible) {
132423                 root.collapse(true, callback, scope);
132424             } else {
132425                 root.collapseChildren(true, callback, scope);
132426             }
132427             if (!animate) {
132428                 view.endBulkUpdate();
132429             }
132430         }
132431     },
132432
132433     /**
132434      * Expand the tree to the path of a particular node.
132435      * @param {String} path The path to expand. The path should include a leading separator.
132436      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
132437      * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
132438      * @param {Function} callback (optional) A function to execute when the expand finishes. The callback will be called with
132439      * (success, lastNode) where success is if the expand was successful and lastNode is the last node that was expanded.
132440      * @param {Object} scope (optional) The scope of the callback function
132441      */
132442     expandPath: function(path, field, separator, callback, scope) {
132443         var me = this,
132444             current = me.getRootNode(),
132445             index = 1,
132446             view = me.getView(),
132447             keys,
132448             expander;
132449
132450         field = field || me.getRootNode().idProperty;
132451         separator = separator || '/';
132452
132453         if (Ext.isEmpty(path)) {
132454             Ext.callback(callback, scope || me, [false, null]);
132455             return;
132456         }
132457
132458         keys = path.split(separator);
132459         if (current.get(field) != keys[1]) {
132460             // invalid root
132461             Ext.callback(callback, scope || me, [false, current]);
132462             return;
132463         }
132464
132465         expander = function(){
132466             if (++index === keys.length) {
132467                 Ext.callback(callback, scope || me, [true, current]);
132468                 return;
132469             }
132470             var node = current.findChild(field, keys[index]);
132471             if (!node) {
132472                 Ext.callback(callback, scope || me, [false, current]);
132473                 return;
132474             }
132475             current = node;
132476             current.expand(false, expander);
132477         };
132478         current.expand(false, expander);
132479     },
132480
132481     /**
132482      * Expand the tree to the path of a particular node, then select it.
132483      * @param {String} path The path to select. The path should include a leading separator.
132484      * @param {String} field (optional) The field to get the data from. Defaults to the model idProperty.
132485      * @param {String} separator (optional) A separator to use. Defaults to `'/'`.
132486      * @param {Function} callback (optional) A function to execute when the select finishes. The callback will be called with
132487      * (bSuccess, oLastNode) where bSuccess is if the select was successful and oLastNode is the last node that was expanded.
132488      * @param {Object} scope (optional) The scope of the callback function
132489      */
132490     selectPath: function(path, field, separator, callback, scope) {
132491         var me = this,
132492             keys,
132493             last;
132494
132495         field = field || me.getRootNode().idProperty;
132496         separator = separator || '/';
132497
132498         keys = path.split(separator);
132499         last = keys.pop();
132500
132501         me.expandPath(keys.join(separator), field, separator, function(success, node){
132502             var doSuccess = false;
132503             if (success && node) {
132504                 node = node.findChild(field, last);
132505                 if (node) {
132506                     me.getSelectionModel().select(node);
132507                     Ext.callback(callback, scope || me, [true, node]);
132508                     doSuccess = true;
132509                 }
132510             } else if (node === me.getRootNode()) {
132511                 doSuccess = true;
132512             }
132513             Ext.callback(callback, scope || me, [doSuccess, node]);
132514         }, me);
132515     }
132516 });
132517
132518 /**
132519  * @class Ext.view.DragZone
132520  * @extends Ext.dd.DragZone
132521  * @private
132522  */
132523 Ext.define('Ext.view.DragZone', {
132524     extend: 'Ext.dd.DragZone',
132525     containerScroll: false,
132526
132527     constructor: function(config) {
132528         var me = this;
132529
132530         Ext.apply(me, config);
132531
132532         // Create a ddGroup unless one has been configured.
132533         // User configuration of ddGroups allows users to specify which
132534         // DD instances can interact with each other. Using one
132535         // based on the id of the View would isolate it and mean it can only
132536         // interact with a DropZone on the same View also using a generated ID.
132537         if (!me.ddGroup) {
132538             me.ddGroup = 'view-dd-zone-' + me.view.id;
132539         }
132540
132541         // Ext.dd.DragDrop instances are keyed by the ID of their encapsulating element.
132542         // So a View's DragZone cannot use the View's main element because the DropZone must use that
132543         // because the DropZone may need to scroll on hover at a scrolling boundary, and it is the View's
132544         // main element which handles scrolling.
132545         // We use the View's parent element to drag from. Ideally, we would use the internal structure, but that
132546         // is transient; DataView's recreate the internal structure dynamically as data changes.
132547         // TODO: Ext 5.0 DragDrop must allow multiple DD objects to share the same element.
132548         me.callParent([me.view.el.dom.parentNode]);
132549
132550         me.ddel = Ext.get(document.createElement('div'));
132551         me.ddel.addCls(Ext.baseCSSPrefix + 'grid-dd-wrap');
132552     },
132553
132554     init: function(id, sGroup, config) {
132555         this.initTarget(id, sGroup, config);
132556         this.view.mon(this.view, {
132557             itemmousedown: this.onItemMouseDown,
132558             scope: this
132559         });
132560     },
132561
132562     onItemMouseDown: function(view, record, item, index, e) {
132563         if (!this.isPreventDrag(e, record, item, index)) {
132564             this.handleMouseDown(e);
132565
132566             // If we want to allow dragging of multi-selections, then veto the following handlers (which, in the absence of ctrlKey, would deselect)
132567             // if the mousedowned record is selected
132568             if (view.getSelectionModel().selectionMode == 'MULTI' && !e.ctrlKey && view.getSelectionModel().isSelected(record)) {
132569                 return false;
132570             }
132571         }
132572     },
132573
132574     // private template method
132575     isPreventDrag: function(e) {
132576         return false;
132577     },
132578
132579     getDragData: function(e) {
132580         var view = this.view,
132581             item = e.getTarget(view.getItemSelector()),
132582             record, selectionModel, records;
132583
132584         if (item) {
132585             record = view.getRecord(item);
132586             selectionModel = view.getSelectionModel();
132587             records = selectionModel.getSelection();
132588             return {
132589                 copy: this.view.copy || (this.view.allowCopy && e.ctrlKey),
132590                 event: new Ext.EventObjectImpl(e),
132591                 view: view,
132592                 ddel: this.ddel,
132593                 item: item,
132594                 records: records,
132595                 fromPosition: Ext.fly(item).getXY()
132596             };
132597         }
132598     },
132599
132600     onInitDrag: function(x, y) {
132601         var me = this,
132602             data = me.dragData,
132603             view = data.view,
132604             selectionModel = view.getSelectionModel(),
132605             record = view.getRecord(data.item),
132606             e = data.event;
132607
132608         // Update the selection to match what would have been selected if the user had
132609         // done a full click on the target node rather than starting a drag from it
132610         if (!selectionModel.isSelected(record) || e.hasModifier()) {
132611             selectionModel.selectWithEvent(record, e, true);
132612         }
132613         data.records = selectionModel.getSelection();
132614
132615         me.ddel.update(me.getDragText());
132616         me.proxy.update(me.ddel.dom);
132617         me.onStartDrag(x, y);
132618         return true;
132619     },
132620
132621     getDragText: function() {
132622         var count = this.dragData.records.length;
132623         return Ext.String.format(this.dragText, count, count == 1 ? '' : 's');
132624     },
132625
132626     getRepairXY : function(e, data){
132627         return data ? data.fromPosition : false;
132628     }
132629 });
132630 Ext.define('Ext.tree.ViewDragZone', {
132631     extend: 'Ext.view.DragZone',
132632
132633     isPreventDrag: function(e, record) {
132634         return (record.get('allowDrag') === false) || !!e.getTarget(this.view.expanderSelector);
132635     },
132636     
132637     afterRepair: function() {
132638         var me = this,
132639             view = me.view,
132640             selectedRowCls = view.selectedItemCls,
132641             records = me.dragData.records,
132642             fly = Ext.fly;
132643         
132644         if (Ext.enableFx && me.repairHighlight) {
132645             // Roll through all records and highlight all the ones we attempted to drag.
132646             Ext.Array.forEach(records, function(record) {
132647                 // anonymous fns below, don't hoist up unless below is wrapped in
132648                 // a self-executing function passing in item.
132649                 var item = view.getNode(record);
132650                 
132651                 // We must remove the selected row class before animating, because
132652                 // the selected row class declares !important on its background-color.
132653                 fly(item.firstChild).highlight(me.repairHighlightColor, {
132654                     listeners: {
132655                         beforeanimate: function() {
132656                             if (view.isSelected(item)) {
132657                                 fly(item).removeCls(selectedRowCls);
132658                             }
132659                         },
132660                         afteranimate: function() {
132661                             if (view.isSelected(item)) {
132662                                 fly(item).addCls(selectedRowCls);
132663                             }
132664                         }
132665                     }
132666                 });
132667             });
132668         }
132669         me.dragging = false;
132670     }
132671 });
132672 /**
132673  * @class Ext.tree.ViewDropZone
132674  * @extends Ext.view.DropZone
132675  * @private
132676  */
132677 Ext.define('Ext.tree.ViewDropZone', {
132678     extend: 'Ext.view.DropZone',
132679
132680     /**
132681      * @cfg {Boolean} allowParentInsert
132682      * Allow inserting a dragged node between an expanded parent node and its first child that will become a
132683      * sibling of the parent when dropped.
132684      */
132685     allowParentInserts: false,
132686  
132687     /**
132688      * @cfg {String} allowContainerDrop
132689      * True if drops on the tree container (outside of a specific tree node) are allowed.
132690      */
132691     allowContainerDrops: false,
132692
132693     /**
132694      * @cfg {String} appendOnly
132695      * True if the tree should only allow append drops (use for trees which are sorted).
132696      */
132697     appendOnly: false,
132698
132699     /**
132700      * @cfg {String} expandDelay
132701      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
132702      * over the target.
132703      */
132704     expandDelay : 500,
132705
132706     indicatorCls: 'x-tree-ddindicator',
132707
132708     // private
132709     expandNode : function(node) {
132710         var view = this.view;
132711         if (!node.isLeaf() && !node.isExpanded()) {
132712             view.expand(node);
132713             this.expandProcId = false;
132714         }
132715     },
132716
132717     // private
132718     queueExpand : function(node) {
132719         this.expandProcId = Ext.Function.defer(this.expandNode, this.expandDelay, this, [node]);
132720     },
132721
132722     // private
132723     cancelExpand : function() {
132724         if (this.expandProcId) {
132725             clearTimeout(this.expandProcId);
132726             this.expandProcId = false;
132727         }
132728     },
132729
132730     getPosition: function(e, node) {
132731         var view = this.view,
132732             record = view.getRecord(node),
132733             y = e.getPageY(),
132734             noAppend = record.isLeaf(),
132735             noBelow = false,
132736             region = Ext.fly(node).getRegion(),
132737             fragment;
132738
132739         // If we are dragging on top of the root node of the tree, we always want to append.
132740         if (record.isRoot()) {
132741             return 'append';
132742         }
132743
132744         // Return 'append' if the node we are dragging on top of is not a leaf else return false.
132745         if (this.appendOnly) {
132746             return noAppend ? false : 'append';
132747         }
132748
132749         if (!this.allowParentInsert) {
132750             noBelow = record.hasChildNodes() && record.isExpanded();
132751         }
132752
132753         fragment = (region.bottom - region.top) / (noAppend ? 2 : 3);
132754         if (y >= region.top && y < (region.top + fragment)) {
132755             return 'before';
132756         }
132757         else if (!noBelow && (noAppend || (y >= (region.bottom - fragment) && y <= region.bottom))) {
132758             return 'after';
132759         }
132760         else {
132761             return 'append';
132762         }
132763     },
132764
132765     isValidDropPoint : function(node, position, dragZone, e, data) {
132766         if (!node || !data.item) {
132767             return false;
132768         }
132769
132770         var view = this.view,
132771             targetNode = view.getRecord(node),
132772             draggedRecords = data.records,
132773             dataLength = draggedRecords.length,
132774             ln = draggedRecords.length,
132775             i, record;
132776
132777         // No drop position, or dragged records: invalid drop point
132778         if (!(targetNode && position && dataLength)) {
132779             return false;
132780         }
132781
132782         // If the targetNode is within the folder we are dragging
132783         for (i = 0; i < ln; i++) {
132784             record = draggedRecords[i];
132785             if (record.isNode && record.contains(targetNode)) {
132786                 return false;
132787             }
132788         }
132789         
132790         // Respect the allowDrop field on Tree nodes
132791         if (position === 'append' && targetNode.get('allowDrop') === false) {
132792             return false;
132793         }
132794         else if (position != 'append' && targetNode.parentNode.get('allowDrop') === false) {
132795             return false;
132796         }
132797
132798         // If the target record is in the dragged dataset, then invalid drop
132799         if (Ext.Array.contains(draggedRecords, targetNode)) {
132800              return false;
132801         }
132802
132803         // @TODO: fire some event to notify that there is a valid drop possible for the node you're dragging
132804         // Yes: this.fireViewEvent(blah....) fires an event through the owning View.
132805         return true;
132806     },
132807
132808     onNodeOver : function(node, dragZone, e, data) {
132809         var position = this.getPosition(e, node),
132810             returnCls = this.dropNotAllowed,
132811             view = this.view,
132812             targetNode = view.getRecord(node),
132813             indicator = this.getIndicator(),
132814             indicatorX = 0,
132815             indicatorY = 0;
132816
132817         // auto node expand check
132818         this.cancelExpand();
132819         if (position == 'append' && !this.expandProcId && !Ext.Array.contains(data.records, targetNode) && !targetNode.isLeaf() && !targetNode.isExpanded()) {
132820             this.queueExpand(targetNode);
132821         }
132822             
132823             
132824         if (this.isValidDropPoint(node, position, dragZone, e, data)) {
132825             this.valid = true;
132826             this.currentPosition = position;
132827             this.overRecord = targetNode;
132828
132829             indicator.setWidth(Ext.fly(node).getWidth());
132830             indicatorY = Ext.fly(node).getY() - Ext.fly(view.el).getY() - 1;
132831
132832             /*
132833              * In the code below we show the proxy again. The reason for doing this is showing the indicator will
132834              * call toFront, causing it to get a new z-index which can sometimes push the proxy behind it. We always 
132835              * want the proxy to be above, so calling show on the proxy will call toFront and bring it forward.
132836              */
132837             if (position == 'before') {
132838                 returnCls = targetNode.isFirst() ? Ext.baseCSSPrefix + 'tree-drop-ok-above' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
132839                 indicator.showAt(0, indicatorY);
132840                 dragZone.proxy.show();
132841             } else if (position == 'after') {
132842                 returnCls = targetNode.isLast() ? Ext.baseCSSPrefix + 'tree-drop-ok-below' : Ext.baseCSSPrefix + 'tree-drop-ok-between';
132843                 indicatorY += Ext.fly(node).getHeight();
132844                 indicator.showAt(0, indicatorY);
132845                 dragZone.proxy.show();
132846             } else {
132847                 returnCls = Ext.baseCSSPrefix + 'tree-drop-ok-append';
132848                 // @TODO: set a class on the parent folder node to be able to style it
132849                 indicator.hide();
132850             }
132851         } else {
132852             this.valid = false;
132853         }
132854
132855         this.currentCls = returnCls;
132856         return returnCls;
132857     },
132858
132859     onContainerOver : function(dd, e, data) {
132860         return e.getTarget('.' + this.indicatorCls) ? this.currentCls : this.dropNotAllowed;
132861     },
132862     
132863     notifyOut: function() {
132864         this.callParent(arguments);
132865         this.cancelExpand();
132866     },
132867
132868     handleNodeDrop : function(data, targetNode, position) {
132869         var me = this,
132870             view = me.view,
132871             parentNode = targetNode.parentNode,
132872             store = view.getStore(),
132873             recordDomNodes = [],
132874             records, i, len,
132875             insertionMethod, argList,
132876             needTargetExpand,
132877             transferData,
132878             processDrop;
132879
132880         // If the copy flag is set, create a copy of the Models with the same IDs
132881         if (data.copy) {
132882             records = data.records;
132883             data.records = [];
132884             for (i = 0, len = records.length; i < len; i++) {
132885                 data.records.push(Ext.apply({}, records[i].data));
132886             }
132887         }
132888
132889         // Cancel any pending expand operation
132890         me.cancelExpand();
132891
132892         // Grab a reference to the correct node insertion method.
132893         // Create an arg list array intended for the apply method of the
132894         // chosen node insertion method.
132895         // Ensure the target object for the method is referenced by 'targetNode'
132896         if (position == 'before') {
132897             insertionMethod = parentNode.insertBefore;
132898             argList = [null, targetNode];
132899             targetNode = parentNode;
132900         }
132901         else if (position == 'after') {
132902             if (targetNode.nextSibling) {
132903                 insertionMethod = parentNode.insertBefore;
132904                 argList = [null, targetNode.nextSibling];
132905             }
132906             else {
132907                 insertionMethod = parentNode.appendChild;
132908                 argList = [null];
132909             }
132910             targetNode = parentNode;
132911         }
132912         else {
132913             if (!targetNode.isExpanded()) {
132914                 needTargetExpand = true;
132915             }
132916             insertionMethod = targetNode.appendChild;
132917             argList = [null];
132918         }
132919
132920         // A function to transfer the data into the destination tree
132921         transferData = function() {
132922             var node;
132923             for (i = 0, len = data.records.length; i < len; i++) {
132924                 argList[0] = data.records[i];
132925                 node = insertionMethod.apply(targetNode, argList);
132926                 
132927                 if (Ext.enableFx && me.dropHighlight) {
132928                     recordDomNodes.push(view.getNode(node));
132929                 }
132930             }
132931             
132932             // Kick off highlights after everything's been inserted, so they are
132933             // more in sync without insertion/render overhead.
132934             if (Ext.enableFx && me.dropHighlight) {
132935                 //FIXME: the check for n.firstChild is not a great solution here. Ideally the line should simply read 
132936                 //Ext.fly(n.firstChild) but this yields errors in IE6 and 7. See ticket EXTJSIV-1705 for more details
132937                 Ext.Array.forEach(recordDomNodes, function(n) {
132938                     if (n) {
132939                         Ext.fly(n.firstChild ? n.firstChild : n).highlight(me.dropHighlightColor);
132940                     }
132941                 });
132942             }
132943         };
132944
132945         // If dropping right on an unexpanded node, transfer the data after it is expanded.
132946         if (needTargetExpand) {
132947             targetNode.expand(false, transferData);
132948         }
132949         // Otherwise, call the data transfer function immediately
132950         else {
132951             transferData();
132952         }
132953     }
132954 });
132955 /**
132956  * This plugin provides drag and/or drop functionality for a TreeView.
132957  *
132958  * It creates a specialized instance of {@link Ext.dd.DragZone DragZone} which knows how to drag out of a
132959  * {@link Ext.tree.View TreeView} and loads the data object which is passed to a cooperating
132960  * {@link Ext.dd.DragZone DragZone}'s methods with the following properties:
132961  *
132962  *   - copy : Boolean
132963  *
132964  *     The value of the TreeView's `copy` property, or `true` if the TreeView was configured with `allowCopy: true` *and*
132965  *     the control key was pressed when the drag operation was begun.
132966  *
132967  *   - view : TreeView
132968  *
132969  *     The source TreeView from which the drag originated.
132970  *
132971  *   - ddel : HtmlElement
132972  *
132973  *     The drag proxy element which moves with the mouse
132974  *
132975  *   - item : HtmlElement
132976  *
132977  *     The TreeView node upon which the mousedown event was registered.
132978  *
132979  *   - records : Array
132980  *
132981  *     An Array of {@link Ext.data.Model Models} representing the selected data being dragged from the source TreeView.
132982  *
132983  * It also creates a specialized instance of {@link Ext.dd.DropZone} which cooperates with other DropZones which are
132984  * members of the same ddGroup which processes such data objects.
132985  *
132986  * Adding this plugin to a view means that two new events may be fired from the client TreeView, {@link #beforedrop} and
132987  * {@link #drop}.
132988  *
132989  * Note that the plugin must be added to the tree view, not to the tree panel. For example using viewConfig:
132990  *
132991  *     viewConfig: {
132992  *         plugins: { ptype: 'treeviewdragdrop' }
132993  *     }
132994  */
132995 Ext.define('Ext.tree.plugin.TreeViewDragDrop', {
132996     extend: 'Ext.AbstractPlugin',
132997     alias: 'plugin.treeviewdragdrop',
132998
132999     uses: [
133000         'Ext.tree.ViewDragZone',
133001         'Ext.tree.ViewDropZone'
133002     ],
133003
133004     /**
133005      * @event beforedrop
133006      *
133007      * **This event is fired through the TreeView. Add listeners to the TreeView object**
133008      *
133009      * Fired when a drop gesture has been triggered by a mouseup event in a valid drop position in the TreeView.
133010      *
133011      * @param {HTMLElement} node The TreeView node **if any** over which the mouse was positioned.
133012      *
133013      * Returning `false` to this event signals that the drop gesture was invalid, and if the drag proxy will animate
133014      * back to the point from which the drag began.
133015      *
133016      * Returning `0` To this event signals that the data transfer operation should not take place, but that the gesture
133017      * was valid, and that the repair operation should not take place.
133018      *
133019      * Any other return value continues with the data transfer operation.
133020      *
133021      * @param {Object} data The data object gathered at mousedown time by the cooperating
133022      * {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following
133023      * properties:
133024      * @param {Boolean} data.copy The value of the TreeView's `copy` property, or `true` if the TreeView was configured with
133025      * `allowCopy: true` and the control key was pressed when the drag operation was begun
133026      * @param {Ext.tree.View} data.view The source TreeView from which the drag originated.
133027      * @param {HTMLElement} data.ddel The drag proxy element which moves with the mouse
133028      * @param {HTMLElement} data.item The TreeView node upon which the mousedown event was registered.
133029      * @param {Ext.data.Model[]} data.records An Array of {@link Ext.data.Model Model}s representing the selected data being
133030      * dragged from the source TreeView.
133031      *
133032      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
133033      *
133034      * @param {String} dropPosition `"before"`, `"after"` or `"append"` depending on whether the mouse is above or below
133035      * the midline of the node, or the node is a branch node which accepts new child nodes.
133036      *
133037      * @param {Function} dropFunction A function to call to complete the data transfer operation and either move or copy
133038      * Model instances from the source View's Store to the destination View's Store.
133039      *
133040      * This is useful when you want to perform some kind of asynchronous processing before confirming the drop, such as
133041      * an {@link Ext.window.MessageBox#confirm confirm} call, or an Ajax request.
133042      *
133043      * Return `0` from this event handler, and call the `dropFunction` at any time to perform the data transfer.
133044      */
133045
133046     /**
133047      * @event drop
133048      *
133049      * **This event is fired through the TreeView. Add listeners to the TreeView object** Fired when a drop operation
133050      * has been completed and the data has been moved or copied.
133051      *
133052      * @param {HTMLElement} node The TreeView node **if any** over which the mouse was positioned.
133053      *
133054      * @param {Object} data The data object gathered at mousedown time by the cooperating
133055      * {@link Ext.dd.DragZone DragZone}'s {@link Ext.dd.DragZone#getDragData getDragData} method it contains the following
133056      * properties:
133057      * @param {Boolean} data.copy The value of the TreeView's `copy` property, or `true` if the TreeView was configured with
133058      * `allowCopy: true` and the control key was pressed when the drag operation was begun
133059      * @param {Ext.tree.View} data.view The source TreeView from which the drag originated.
133060      * @param {HTMLElement} data.ddel The drag proxy element which moves with the mouse
133061      * @param {HTMLElement} data.item The TreeView node upon which the mousedown event was registered.
133062      * @param {Ext.data.Model[]} data.records An Array of {@link Ext.data.Model Model}s representing the selected data being
133063      * dragged from the source TreeView.
133064      *
133065      * @param {Ext.data.Model} overModel The Model over which the drop gesture took place.
133066      *
133067      * @param {String} dropPosition `"before"`, `"after"` or `"append"` depending on whether the mouse is above or below
133068      * the midline of the node, or the node is a branch node which accepts new child nodes.
133069      */
133070
133071     dragText : '{0} selected node{1}',
133072
133073     /**
133074      * @cfg {Boolean} allowParentInsert
133075      * Allow inserting a dragged node between an expanded parent node and its first child that will become a sibling of
133076      * the parent when dropped.
133077      */
133078     allowParentInserts: false,
133079
133080     /**
133081      * @cfg {String} allowContainerDrop
133082      * True if drops on the tree container (outside of a specific tree node) are allowed.
133083      */
133084     allowContainerDrops: false,
133085
133086     /**
133087      * @cfg {String} appendOnly
133088      * True if the tree should only allow append drops (use for trees which are sorted).
133089      */
133090     appendOnly: false,
133091
133092     /**
133093      * @cfg {String} ddGroup
133094      * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
133095      * DropZone used by this plugin will only interact with other drag drop objects in the same group.
133096      */
133097     ddGroup : "TreeDD",
133098
133099     /**
133100      * @cfg {String} dragGroup
133101      * The ddGroup to which the DragZone will belong.
133102      *
133103      * This defines which other DropZones the DragZone will interact with. Drag/DropZones only interact with other
133104      * Drag/DropZones which are members of the same ddGroup.
133105      */
133106
133107     /**
133108      * @cfg {String} dropGroup
133109      * The ddGroup to which the DropZone will belong.
133110      *
133111      * This defines which other DragZones the DropZone will interact with. Drag/DropZones only interact with other
133112      * Drag/DropZones which are members of the same ddGroup.
133113      */
133114
133115     /**
133116      * @cfg {String} expandDelay
133117      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node over the
133118      * target.
133119      */
133120     expandDelay : 1000,
133121
133122     /**
133123      * @cfg {Boolean} enableDrop
133124      * Set to `false` to disallow the View from accepting drop gestures.
133125      */
133126     enableDrop: true,
133127
133128     /**
133129      * @cfg {Boolean} enableDrag
133130      * Set to `false` to disallow dragging items from the View.
133131      */
133132     enableDrag: true,
133133
133134     /**
133135      * @cfg {String} nodeHighlightColor
133136      * The color to use when visually highlighting the dragged or dropped node (default value is light blue).
133137      * The color must be a 6 digit hex value, without a preceding '#'. See also {@link #nodeHighlightOnDrop} and
133138      * {@link #nodeHighlightOnRepair}.
133139      */
133140     nodeHighlightColor: 'c3daf9',
133141
133142     /**
133143      * @cfg {Boolean} nodeHighlightOnDrop
133144      * Whether or not to highlight any nodes after they are
133145      * successfully dropped on their target. Defaults to the value of `Ext.enableFx`.
133146      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnRepair}.
133147      */
133148     nodeHighlightOnDrop: Ext.enableFx,
133149
133150     /**
133151      * @cfg {Boolean} nodeHighlightOnRepair
133152      * Whether or not to highlight any nodes after they are
133153      * repaired from an unsuccessful drag/drop. Defaults to the value of `Ext.enableFx`.
133154      * See also {@link #nodeHighlightColor} and {@link #nodeHighlightOnDrop}.
133155      */
133156     nodeHighlightOnRepair: Ext.enableFx,
133157
133158     init : function(view) {
133159         view.on('render', this.onViewRender, this, {single: true});
133160     },
133161
133162     /**
133163      * @private
133164      * AbstractComponent calls destroy on all its plugins at destroy time.
133165      */
133166     destroy: function() {
133167         Ext.destroy(this.dragZone, this.dropZone);
133168     },
133169
133170     onViewRender : function(view) {
133171         var me = this;
133172
133173         if (me.enableDrag) {
133174             me.dragZone = Ext.create('Ext.tree.ViewDragZone', {
133175                 view: view,
133176                 ddGroup: me.dragGroup || me.ddGroup,
133177                 dragText: me.dragText,
133178                 repairHighlightColor: me.nodeHighlightColor,
133179                 repairHighlight: me.nodeHighlightOnRepair
133180             });
133181         }
133182
133183         if (me.enableDrop) {
133184             me.dropZone = Ext.create('Ext.tree.ViewDropZone', {
133185                 view: view,
133186                 ddGroup: me.dropGroup || me.ddGroup,
133187                 allowContainerDrops: me.allowContainerDrops,
133188                 appendOnly: me.appendOnly,
133189                 allowParentInserts: me.allowParentInserts,
133190                 expandDelay: me.expandDelay,
133191                 dropHighlightColor: me.nodeHighlightColor,
133192                 dropHighlight: me.nodeHighlightOnDrop
133193             });
133194         }
133195     }
133196 });
133197 /**
133198  * @class Ext.util.Cookies
133199
133200 Utility class for setting/reading values from browser cookies.
133201 Values can be written using the {@link #set} method.
133202 Values can be read using the {@link #get} method.
133203 A cookie can be invalidated on the client machine using the {@link #clear} method.
133204
133205  * @markdown
133206  * @singleton
133207  */
133208 Ext.define('Ext.util.Cookies', {
133209     singleton: true,
133210     
133211     /**
133212      * Create a cookie with the specified name and value. Additional settings
133213      * for the cookie may be optionally specified (for example: expiration,
133214      * access restriction, SSL).
133215      * @param {String} name The name of the cookie to set. 
133216      * @param {Object} value The value to set for the cookie.
133217      * @param {Object} expires (Optional) Specify an expiration date the
133218      * cookie is to persist until.  Note that the specified Date object will
133219      * be converted to Greenwich Mean Time (GMT). 
133220      * @param {String} path (Optional) Setting a path on the cookie restricts
133221      * access to pages that match that path. Defaults to all pages (<tt>'/'</tt>). 
133222      * @param {String} domain (Optional) Setting a domain restricts access to
133223      * pages on a given domain (typically used to allow cookie access across
133224      * subdomains). For example, "sencha.com" will create a cookie that can be
133225      * accessed from any subdomain of sencha.com, including www.sencha.com,
133226      * support.sencha.com, etc.
133227      * @param {Boolean} secure (Optional) Specify true to indicate that the cookie
133228      * should only be accessible via SSL on a page using the HTTPS protocol.
133229      * Defaults to <tt>false</tt>. Note that this will only work if the page
133230      * calling this code uses the HTTPS protocol, otherwise the cookie will be
133231      * created with default options.
133232      */
133233     set : function(name, value){
133234         var argv = arguments,
133235             argc = arguments.length,
133236             expires = (argc > 2) ? argv[2] : null,
133237             path = (argc > 3) ? argv[3] : '/',
133238             domain = (argc > 4) ? argv[4] : null,
133239             secure = (argc > 5) ? argv[5] : false;
133240             
133241         document.cookie = name + "=" + escape(value) + ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + ((path === null) ? "" : ("; path=" + path)) + ((domain === null) ? "" : ("; domain=" + domain)) + ((secure === true) ? "; secure" : "");
133242     },
133243
133244     /**
133245      * Retrieves cookies that are accessible by the current page. If a cookie
133246      * does not exist, <code>get()</code> returns <tt>null</tt>.  The following
133247      * example retrieves the cookie called "valid" and stores the String value
133248      * in the variable <tt>validStatus</tt>.
133249      * <pre><code>
133250      * var validStatus = Ext.util.Cookies.get("valid");
133251      * </code></pre>
133252      * @param {String} name The name of the cookie to get
133253      * @return {Object} Returns the cookie value for the specified name;
133254      * null if the cookie name does not exist.
133255      */
133256     get : function(name){
133257         var arg = name + "=",
133258             alen = arg.length,
133259             clen = document.cookie.length,
133260             i = 0,
133261             j = 0;
133262             
133263         while(i < clen){
133264             j = i + alen;
133265             if(document.cookie.substring(i, j) == arg){
133266                 return this.getCookieVal(j);
133267             }
133268             i = document.cookie.indexOf(" ", i) + 1;
133269             if(i === 0){
133270                 break;
133271             }
133272         }
133273         return null;
133274     },
133275
133276     /**
133277      * Removes a cookie with the provided name from the browser
133278      * if found by setting its expiration date to sometime in the past. 
133279      * @param {String} name The name of the cookie to remove
133280      * @param {String} path (optional) The path for the cookie. This must be included if you included a path while setting the cookie.
133281      */
133282     clear : function(name, path){
133283         if(this.get(name)){
133284             path = path || '/';
133285             document.cookie = name + '=' + '; expires=Thu, 01-Jan-70 00:00:01 GMT; path=' + path;
133286         }
133287     },
133288     
133289     /**
133290      * @private
133291      */
133292     getCookieVal : function(offset){
133293         var endstr = document.cookie.indexOf(";", offset);
133294         if(endstr == -1){
133295             endstr = document.cookie.length;
133296         }
133297         return unescape(document.cookie.substring(offset, endstr));
133298     }
133299 });
133300
133301 /**
133302  * @class Ext.util.CSS
133303  * Utility class for manipulating CSS rules
133304  * @singleton
133305  */
133306 Ext.define('Ext.util.CSS', function() {
133307     var rules = null;
133308     var doc = document;
133309
133310     var camelRe = /(-[a-z])/gi;
133311     var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
133312
133313     return {
133314
133315         singleton: true,
133316
133317         constructor: function() {
133318             this.rules = {};
133319             this.initialized = false;
133320         },
133321
133322         /**
133323          * Creates a stylesheet from a text blob of rules.
133324          * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
133325          * @param {String} cssText The text containing the css rules
133326          * @param {String} id An id to add to the stylesheet for later removal
133327          * @return {CSSStyleSheet}
133328          */
133329         createStyleSheet : function(cssText, id) {
133330             var ss,
133331                 head = doc.getElementsByTagName("head")[0],
133332                 styleEl = doc.createElement("style");
133333
133334             styleEl.setAttribute("type", "text/css");
133335             if (id) {
133336                styleEl.setAttribute("id", id);
133337             }
133338
133339             if (Ext.isIE) {
133340                head.appendChild(styleEl);
133341                ss = styleEl.styleSheet;
133342                ss.cssText = cssText;
133343             } else {
133344                 try{
133345                     styleEl.appendChild(doc.createTextNode(cssText));
133346                 } catch(e) {
133347                    styleEl.cssText = cssText;
133348                 }
133349                 head.appendChild(styleEl);
133350                 ss = styleEl.styleSheet ? styleEl.styleSheet : (styleEl.sheet || doc.styleSheets[doc.styleSheets.length-1]);
133351             }
133352             this.cacheStyleSheet(ss);
133353             return ss;
133354         },
133355
133356         /**
133357          * Removes a style or link tag by id
133358          * @param {String} id The id of the tag
133359          */
133360         removeStyleSheet : function(id) {
133361             var existing = document.getElementById(id);
133362             if (existing) {
133363                 existing.parentNode.removeChild(existing);
133364             }
133365         },
133366
133367         /**
133368          * Dynamically swaps an existing stylesheet reference for a new one
133369          * @param {String} id The id of an existing link tag to remove
133370          * @param {String} url The href of the new stylesheet to include
133371          */
133372         swapStyleSheet : function(id, url) {
133373             var doc = document;
133374             this.removeStyleSheet(id);
133375             var ss = doc.createElement("link");
133376             ss.setAttribute("rel", "stylesheet");
133377             ss.setAttribute("type", "text/css");
133378             ss.setAttribute("id", id);
133379             ss.setAttribute("href", url);
133380             doc.getElementsByTagName("head")[0].appendChild(ss);
133381         },
133382
133383         /**
133384          * Refresh the rule cache if you have dynamically added stylesheets
133385          * @return {Object} An object (hash) of rules indexed by selector
133386          */
133387         refreshCache : function() {
133388             return this.getRules(true);
133389         },
133390
133391         // private
133392         cacheStyleSheet : function(ss) {
133393             if(!rules){
133394                 rules = {};
133395             }
133396             try {// try catch for cross domain access issue
133397                 var ssRules = ss.cssRules || ss.rules,
133398                     selectorText,
133399                     i = ssRules.length - 1,
133400                     j,
133401                     selectors;
133402
133403                 for (; i >= 0; --i) {
133404                     selectorText = ssRules[i].selectorText;
133405                     if (selectorText) {
133406
133407                         // Split in case there are multiple, comma-delimited selectors
133408                         selectorText = selectorText.split(',');
133409                         selectors = selectorText.length;
133410                         for (j = 0; j < selectors; j++) {
133411                             rules[Ext.String.trim(selectorText[j]).toLowerCase()] = ssRules[i];
133412                         }
133413                     }
133414                 }
133415             } catch(e) {}
133416         },
133417
133418         /**
133419         * Gets all css rules for the document
133420         * @param {Boolean} refreshCache true to refresh the internal cache
133421         * @return {Object} An object (hash) of rules indexed by selector
133422         */
133423         getRules : function(refreshCache) {
133424             if (rules === null || refreshCache) {
133425                 rules = {};
133426                 var ds = doc.styleSheets,
133427                     i = 0,
133428                     len = ds.length;
133429
133430                 for (; i < len; i++) {
133431                     try {
133432                         if (!ds[i].disabled) {
133433                             this.cacheStyleSheet(ds[i]);
133434                         }
133435                     } catch(e) {}
133436                 }
133437             }
133438             return rules;
133439         },
133440
133441         /**
133442          * Gets an an individual CSS rule by selector(s)
133443          * @param {String/String[]} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
133444          * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
133445          * @return {CSSStyleRule} The CSS rule or null if one is not found
133446          */
133447         getRule: function(selector, refreshCache) {
133448             var rs = this.getRules(refreshCache);
133449             if (!Ext.isArray(selector)) {
133450                 return rs[selector.toLowerCase()];
133451             }
133452             for (var i = 0; i < selector.length; i++) {
133453                 if (rs[selector[i]]) {
133454                     return rs[selector[i].toLowerCase()];
133455                 }
133456             }
133457             return null;
133458         },
133459
133460         /**
133461          * Updates a rule property
133462          * @param {String/String[]} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
133463          * @param {String} property The css property
133464          * @param {String} value The new value for the property
133465          * @return {Boolean} true If a rule was found and updated
133466          */
133467         updateRule : function(selector, property, value){
133468             if (!Ext.isArray(selector)) {
133469                 var rule = this.getRule(selector);
133470                 if (rule) {
133471                     rule.style[property.replace(camelRe, camelFn)] = value;
133472                     return true;
133473                 }
133474             } else {
133475                 for (var i = 0; i < selector.length; i++) {
133476                     if (this.updateRule(selector[i], property, value)) {
133477                         return true;
133478                     }
133479                 }
133480             }
133481             return false;
133482         }
133483     };
133484 }());
133485 /**
133486  * @class Ext.util.History
133487  *
133488  * History management component that allows you to register arbitrary tokens that signify application
133489  * history state on navigation actions.  You can then handle the history {@link #change} event in order
133490  * to reset your application UI to the appropriate state when the user navigates forward or backward through
133491  * the browser history stack.
133492  *
133493  * ## Initializing
133494  * The {@link #init} method of the History object must be called before using History. This sets up the internal
133495  * state and must be the first thing called before using History.
133496  *
133497  * ## Setup
133498  * The History objects requires elements on the page to keep track of the browser history. For older versions of IE,
133499  * an IFrame is required to do the tracking. For other browsers, a hidden field can be used. The history objects expects
133500  * these to be on the page before the {@link #init} method is called. The following markup is suggested in order
133501  * to support all browsers:
133502  *
133503  *     <form id="history-form" class="x-hide-display">
133504  *         <input type="hidden" id="x-history-field" />
133505  *         <iframe id="x-history-frame"></iframe>
133506  *     </form>
133507  *
133508  * @singleton
133509  */
133510 Ext.define('Ext.util.History', {
133511     singleton: true,
133512     alternateClassName: 'Ext.History',
133513     mixins: {
133514         observable: 'Ext.util.Observable'
133515     },
133516
133517     constructor: function() {
133518         var me = this;
133519         me.oldIEMode = Ext.isIE6 || Ext.isIE7 || !Ext.isStrict && Ext.isIE8;
133520         me.iframe = null;
133521         me.hiddenField = null;
133522         me.ready = false;
133523         me.currentToken = null;
133524     },
133525
133526     getHash: function() {
133527         var href = window.location.href,
133528             i = href.indexOf("#");
133529
133530         return i >= 0 ? href.substr(i + 1) : null;
133531     },
133532
133533     doSave: function() {
133534         this.hiddenField.value = this.currentToken;
133535     },
133536
133537
133538     handleStateChange: function(token) {
133539         this.currentToken = token;
133540         this.fireEvent('change', token);
133541     },
133542
133543     updateIFrame: function(token) {
133544         var html = '<html><body><div id="state">' +
133545                     Ext.util.Format.htmlEncode(token) +
133546                     '</div></body></html>';
133547
133548         try {
133549             var doc = this.iframe.contentWindow.document;
133550             doc.open();
133551             doc.write(html);
133552             doc.close();
133553             return true;
133554         } catch (e) {
133555             return false;
133556         }
133557     },
133558
133559     checkIFrame: function () {
133560         var me = this,
133561             contentWindow = me.iframe.contentWindow;
133562
133563         if (!contentWindow || !contentWindow.document) {
133564             Ext.Function.defer(this.checkIFrame, 10, this);
133565             return;
133566         }
133567
133568         var doc = contentWindow.document,
133569             elem = doc.getElementById("state"),
133570             oldToken = elem ? elem.innerText : null,
133571             oldHash = me.getHash();
133572
133573         Ext.TaskManager.start({
133574             run: function () {
133575                 var doc = contentWindow.document,
133576                     elem = doc.getElementById("state"),
133577                     newToken = elem ? elem.innerText : null,
133578                     newHash = me.getHash();
133579
133580                 if (newToken !== oldToken) {
133581                     oldToken = newToken;
133582                     me.handleStateChange(newToken);
133583                     window.top.location.hash = newToken;
133584                     oldHash = newToken;
133585                     me.doSave();
133586                 } else if (newHash !== oldHash) {
133587                     oldHash = newHash;
133588                     me.updateIFrame(newHash);
133589                 }
133590             },
133591             interval: 50,
133592             scope: me
133593         });
133594         me.ready = true;
133595         me.fireEvent('ready', me);
133596     },
133597
133598     startUp: function () {
133599         var me = this;
133600
133601         me.currentToken = me.hiddenField.value || this.getHash();
133602
133603         if (me.oldIEMode) {
133604             me.checkIFrame();
133605         } else {
133606             var hash = me.getHash();
133607             Ext.TaskManager.start({
133608                 run: function () {
133609                     var newHash = me.getHash();
133610                     if (newHash !== hash) {
133611                         hash = newHash;
133612                         me.handleStateChange(hash);
133613                         me.doSave();
133614                     }
133615                 },
133616                 interval: 50,
133617                 scope: me
133618             });
133619             me.ready = true;
133620             me.fireEvent('ready', me);
133621         }
133622
133623     },
133624
133625     /**
133626      * The id of the hidden field required for storing the current history token.
133627      * @type String
133628      * @property
133629      */
133630     fieldId: Ext.baseCSSPrefix + 'history-field',
133631     /**
133632      * The id of the iframe required by IE to manage the history stack.
133633      * @type String
133634      * @property
133635      */
133636     iframeId: Ext.baseCSSPrefix + 'history-frame',
133637
133638     /**
133639      * Initialize the global History instance.
133640      * @param {Boolean} onReady (optional) A callback function that will be called once the history
133641      * component is fully initialized.
133642      * @param {Object} scope (optional) The scope (`this` reference) in which the callback is executed. Defaults to the browser window.
133643      */
133644     init: function (onReady, scope) {
133645         var me = this;
133646
133647         if (me.ready) {
133648             Ext.callback(onReady, scope, [me]);
133649             return;
133650         }
133651
133652         if (!Ext.isReady) {
133653             Ext.onReady(function() {
133654                 me.init(onReady, scope);
133655             });
133656             return;
133657         }
133658
133659         me.hiddenField = Ext.getDom(me.fieldId);
133660
133661         if (me.oldIEMode) {
133662             me.iframe = Ext.getDom(me.iframeId);
133663         }
133664
133665         me.addEvents(
133666             /**
133667              * @event ready
133668              * Fires when the Ext.util.History singleton has been initialized and is ready for use.
133669              * @param {Ext.util.History} The Ext.util.History singleton.
133670              */
133671             'ready',
133672             /**
133673              * @event change
133674              * Fires when navigation back or forwards within the local page's history occurs.
133675              * @param {String} token An identifier associated with the page state at that point in its history.
133676              */
133677             'change'
133678         );
133679
133680         if (onReady) {
133681             me.on('ready', onReady, scope, {single: true});
133682         }
133683         me.startUp();
133684     },
133685
133686     /**
133687      * Add a new token to the history stack. This can be any arbitrary value, although it would
133688      * commonly be the concatenation of a component id and another id marking the specific history
133689      * state of that component. Example usage:
133690      *
133691      *     // Handle tab changes on a TabPanel
133692      *     tabPanel.on('tabchange', function(tabPanel, tab){
133693      *          Ext.History.add(tabPanel.id + ':' + tab.id);
133694      *     });
133695      *
133696      * @param {String} token The value that defines a particular application-specific history state
133697      * @param {Boolean} [preventDuplicates=true] When true, if the passed token matches the current token
133698      * it will not save a new history step. Set to false if the same state can be saved more than once
133699      * at the same history stack location.
133700      */
133701     add: function (token, preventDup) {
133702         var me = this;
133703
133704         if (preventDup !== false) {
133705             if (me.getToken() === token) {
133706                 return true;
133707             }
133708         }
133709
133710         if (me.oldIEMode) {
133711             return me.updateIFrame(token);
133712         } else {
133713             window.top.location.hash = token;
133714             return true;
133715         }
133716     },
133717
133718     /**
133719      * Programmatically steps back one step in browser history (equivalent to the user pressing the Back button).
133720      */
133721     back: function() {
133722         window.history.go(-1);
133723     },
133724
133725     /**
133726      * Programmatically steps forward one step in browser history (equivalent to the user pressing the Forward button).
133727      */
133728     forward: function(){
133729         window.history.go(1);
133730     },
133731
133732     /**
133733      * Retrieves the currently-active history token.
133734      * @return {String} The token
133735      */
133736     getToken: function() {
133737         return this.ready ? this.currentToken : this.getHash();
133738     }
133739 });
133740 /**
133741  * @class Ext.view.TableChunker
133742  * 
133743  * Produces optimized XTemplates for chunks of tables to be
133744  * used in grids, trees and other table based widgets.
133745  *
133746  * @singleton
133747  */
133748 Ext.define('Ext.view.TableChunker', {
133749     singleton: true,
133750     requires: ['Ext.XTemplate'],
133751     metaTableTpl: [
133752         '{[this.openTableWrap()]}',
133753         '<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" border="0" cellspacing="0" cellpadding="0" {[this.embedFullWidth()]}>',
133754             '<tbody>',
133755             '<tr class="' + Ext.baseCSSPrefix + 'grid-header-row">',
133756             '<tpl for="columns">',
133757                 '<th class="' + Ext.baseCSSPrefix + 'grid-col-resizer-{id}" style="width: {width}px; height: 0px;"></th>',
133758             '</tpl>',
133759             '</tr>',
133760             '{[this.openRows()]}',
133761                 '{row}',
133762                 '<tpl for="features">',
133763                     '{[this.embedFeature(values, parent, xindex, xcount)]}',
133764                 '</tpl>',
133765             '{[this.closeRows()]}',
133766             '</tbody>',
133767         '</table>',
133768         '{[this.closeTableWrap()]}'
133769     ],
133770
133771     constructor: function() {
133772         Ext.XTemplate.prototype.recurse = function(values, reference) {
133773             return this.apply(reference ? values[reference] : values);
133774         };
133775     },
133776
133777     embedFeature: function(values, parent, x, xcount) {
133778         var tpl = '';
133779         if (!values.disabled) {
133780             tpl = values.getFeatureTpl(values, parent, x, xcount);
133781         }
133782         return tpl;
133783     },
133784
133785     embedFullWidth: function() {
133786         return 'style="width: {fullWidth}px;"';
133787     },
133788
133789     openRows: function() {
133790         return '<tpl for="rows">';
133791     },
133792
133793     closeRows: function() {
133794         return '</tpl>';
133795     },
133796
133797     metaRowTpl: [
133798         '<tr class="' + Ext.baseCSSPrefix + 'grid-row {addlSelector} {[this.embedRowCls()]}" {[this.embedRowAttr()]}>',
133799             '<tpl for="columns">',
133800                 '<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>',
133801             '</tpl>',
133802         '</tr>'
133803     ],
133804     
133805     firstOrLastCls: function(xindex, xcount) {
133806         var cssCls = '';
133807         if (xindex === 1) {
133808             cssCls = Ext.baseCSSPrefix + 'grid-cell-first';
133809         } else if (xindex === xcount) {
133810             cssCls = Ext.baseCSSPrefix + 'grid-cell-last';
133811         }
133812         return cssCls;
133813     },
133814     
133815     embedRowCls: function() {
133816         return '{rowCls}';
133817     },
133818     
133819     embedRowAttr: function() {
133820         return '{rowAttr}';
133821     },
133822     
133823     openTableWrap: function() {
133824         return '';
133825     },
133826     
133827     closeTableWrap: function() {
133828         return '';
133829     },
133830
133831     getTableTpl: function(cfg, textOnly) {
133832         var tpl,
133833             tableTplMemberFns = {
133834                 openRows: this.openRows,
133835                 closeRows: this.closeRows,
133836                 embedFeature: this.embedFeature,
133837                 embedFullWidth: this.embedFullWidth,
133838                 openTableWrap: this.openTableWrap,
133839                 closeTableWrap: this.closeTableWrap
133840             },
133841             tplMemberFns = {},
133842             features = cfg.features || [],
133843             ln = features.length,
133844             i  = 0,
133845             memberFns = {
133846                 embedRowCls: this.embedRowCls,
133847                 embedRowAttr: this.embedRowAttr,
133848                 firstOrLastCls: this.firstOrLastCls
133849             },
133850             // copy the default
133851             metaRowTpl = Array.prototype.slice.call(this.metaRowTpl, 0),
133852             metaTableTpl;
133853             
133854         for (; i < ln; i++) {
133855             if (!features[i].disabled) {
133856                 features[i].mutateMetaRowTpl(metaRowTpl);
133857                 Ext.apply(memberFns, features[i].getMetaRowTplFragments());
133858                 Ext.apply(tplMemberFns, features[i].getFragmentTpl());
133859                 Ext.apply(tableTplMemberFns, features[i].getTableFragments());
133860             }
133861         }
133862         
133863         metaRowTpl = Ext.create('Ext.XTemplate', metaRowTpl.join(''), memberFns);
133864         cfg.row = metaRowTpl.applyTemplate(cfg);
133865         
133866         metaTableTpl = Ext.create('Ext.XTemplate', this.metaTableTpl.join(''), tableTplMemberFns);
133867         
133868         tpl = metaTableTpl.applyTemplate(cfg);
133869         
133870         // TODO: Investigate eliminating.
133871         if (!textOnly) {
133872             tpl = Ext.create('Ext.XTemplate', tpl, tplMemberFns);
133873         }
133874         return tpl;
133875         
133876     }
133877 });
133878
133879
133880
133881